tzap 0.4.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tzap-0.4.4/.gitignore +25 -0
- tzap-0.4.4/API.md +277 -0
- tzap-0.4.4/Cargo.lock +1220 -0
- tzap-0.4.4/Cargo.toml +55 -0
- tzap-0.4.4/LICENSE +190 -0
- tzap-0.4.4/PKG-INFO +177 -0
- tzap-0.4.4/README.md +155 -0
- tzap-0.4.4/dist-workspace.toml +25 -0
- tzap-0.4.4/docs/pennylane.md +166 -0
- tzap-0.4.4/docs/qiskit.md +141 -0
- tzap-0.4.4/pyproject.toml +58 -0
- tzap-0.4.4/python/tzap/__init__.py +23 -0
- tzap-0.4.4/python/tzap/__main__.py +3 -0
- tzap-0.4.4/python/tzap/_cli.py +72 -0
- tzap-0.4.4/python/tzap/_core.py +92 -0
- tzap-0.4.4/python/tzap/_native.pyi +35 -0
- tzap-0.4.4/python/tzap/pennylane.py +341 -0
- tzap-0.4.4/python/tzap/py.typed +1 -0
- tzap-0.4.4/python/tzap/qiskit.py +205 -0
- tzap-0.4.4/release.toml +9 -0
- tzap-0.4.4/src/bench.rs +550 -0
- tzap-0.4.4/src/cancel.rs +3115 -0
- tzap-0.4.4/src/circuit.rs +553 -0
- tzap-0.4.4/src/cli.rs +293 -0
- tzap-0.4.4/src/decompose.rs +701 -0
- tzap-0.4.4/src/lib.rs +19 -0
- tzap-0.4.4/src/main.rs +288 -0
- tzap-0.4.4/src/optimize.rs +1121 -0
- tzap-0.4.4/src/pass.rs +153 -0
- tzap-0.4.4/src/phase_fold_global_expr.rs +663 -0
- tzap-0.4.4/src/phase_fold_rand.rs +3193 -0
- tzap-0.4.4/src/progress.rs +552 -0
- tzap-0.4.4/src/python.rs +173 -0
- tzap-0.4.4/src/qasm.rs +2024 -0
- tzap-0.4.4/src/super_opt/config.rs +29 -0
- tzap-0.4.4/src/super_opt/error.rs +45 -0
- tzap-0.4.4/src/super_opt/incremental.rs +107 -0
- tzap-0.4.4/src/super_opt/matrix.rs +910 -0
- tzap-0.4.4/src/super_opt/matrix_cache.rs +425 -0
- tzap-0.4.4/src/super_opt/mod.rs +937 -0
- tzap-0.4.4/src/super_opt/synthesis_arena.rs +156 -0
- tzap-0.4.4/src/super_opt/table.rs +597 -0
- tzap-0.4.4/src/super_opt/tests.rs +2394 -0
- tzap-0.4.4/src/unitary.rs +1111 -0
- tzap-0.4.4/tests/cli.rs +1675 -0
- tzap-0.4.4/tests/cli_errors.rs +956 -0
- tzap-0.4.4/tests/fixtures/test.qasm +23 -0
- tzap-0.4.4/tests/fixtures/two_ccx.qasm +6 -0
- tzap-0.4.4/tests/python/test_bindings.py +359 -0
- tzap-0.4.4/tests/python/test_pennylane.py +584 -0
- tzap-0.4.4/tests/python/test_python_cli.py +225 -0
- tzap-0.4.4/tests/python/test_qiskit.py +382 -0
- tzap-0.4.4/tests/python/wheel_smoke.py +36 -0
- tzap-0.4.4/tests/qcec.rs +30 -0
- tzap-0.4.4/tests/qcec_check.py +93 -0
- tzap-0.4.4/uv.lock +1295 -0
tzap-0.4.4/.gitignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/target
|
|
2
|
+
Cargo.lock
|
|
3
|
+
|
|
4
|
+
# Local experiments / scratch work
|
|
5
|
+
/experiments/
|
|
6
|
+
/tmp/
|
|
7
|
+
# Circuits dropped at the repo root are optimizer output and one-off inputs.
|
|
8
|
+
# Anchored, so the tracked benchmarks/ and tests/fixtures/ circuits still count.
|
|
9
|
+
/*.qasm
|
|
10
|
+
|
|
11
|
+
# macOS
|
|
12
|
+
.DS_Store
|
|
13
|
+
|
|
14
|
+
# Python
|
|
15
|
+
__pycache__/
|
|
16
|
+
# Generated by editable Maturin installs.
|
|
17
|
+
/python/tzap/_native*.so
|
|
18
|
+
/python/tzap/_native*.pyd
|
|
19
|
+
/python/tzap/_native*.dylib
|
|
20
|
+
|
|
21
|
+
# Added by cargo
|
|
22
|
+
#
|
|
23
|
+
# already existing elements were commented out
|
|
24
|
+
|
|
25
|
+
#/target
|
tzap-0.4.4/API.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# tzap API
|
|
2
|
+
|
|
3
|
+
## Circuits
|
|
4
|
+
|
|
5
|
+
A `Circuit` holds a list of gates over a fixed number of qubits.
|
|
6
|
+
|
|
7
|
+
```rust
|
|
8
|
+
use tzap::circuit::{Circuit, Gate};
|
|
9
|
+
|
|
10
|
+
let mut circuit = Circuit::new(2);
|
|
11
|
+
circuit.apply(Gate::h(0));
|
|
12
|
+
circuit.apply(Gate::cnot { control: 0, target: 1 });
|
|
13
|
+
circuit.apply(Gate::t(0));
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
To use `measure` gates, allocate classical bits with
|
|
17
|
+
`Circuit::with_cbits(num_qubits, num_cbits)` instead of `Circuit::new`.
|
|
18
|
+
|
|
19
|
+
### Supported gates
|
|
20
|
+
|
|
21
|
+
| Gate | Constructor |
|
|
22
|
+
|------|------------|
|
|
23
|
+
| X | `Gate::x(qubit)` |
|
|
24
|
+
| H | `Gate::h(qubit)` |
|
|
25
|
+
| S | `Gate::s(qubit)` |
|
|
26
|
+
| Sdg | `Gate::sdg(qubit)` |
|
|
27
|
+
| Z | `Gate::z(qubit)` |
|
|
28
|
+
| T | `Gate::t(qubit)` |
|
|
29
|
+
| Tdg | `Gate::tdg(qubit)` |
|
|
30
|
+
| Rz | `Gate::rz(angle, qubit)` |
|
|
31
|
+
| CNOT | `Gate::cnot { control, target }` |
|
|
32
|
+
| CZ | `Gate::cz { control, target }` |
|
|
33
|
+
| Toffoli | `Gate::ccx { control1, control2, target }` |
|
|
34
|
+
| CCZ | `Gate::ccz { control1, control2, target }` |
|
|
35
|
+
| Measure | `Gate::measure { qubit, cbit }` |
|
|
36
|
+
| Reset | `Gate::reset(qubit)` |
|
|
37
|
+
|
|
38
|
+
### QASM I/O
|
|
39
|
+
|
|
40
|
+
Parse from and convert to OpenQASM 2.0. `from_qasm` returns
|
|
41
|
+
`Result<Circuit, String>`:
|
|
42
|
+
|
|
43
|
+
```rust
|
|
44
|
+
use tzap::circuit::Circuit;
|
|
45
|
+
|
|
46
|
+
let circuit = Circuit::from_qasm("
|
|
47
|
+
OPENQASM 2.0;
|
|
48
|
+
include \"qelib1.inc\";
|
|
49
|
+
qreg q[2];
|
|
50
|
+
h q[0];
|
|
51
|
+
cx q[0],q[1];
|
|
52
|
+
").expect("invalid QASM");
|
|
53
|
+
|
|
54
|
+
let qasm_string = circuit.to_qasm();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The QASM parser accepts `ccz` as a native circuit gate. `DecomposeToffoli`
|
|
58
|
+
lowers both `ccx` and `ccz` to Clifford+T.
|
|
59
|
+
|
|
60
|
+
## Optimizing
|
|
61
|
+
|
|
62
|
+
`tzap::optimize` runs the same pipelines the `tzap` CLI does — including
|
|
63
|
+
`-O3`'s decompose → cancel → superoptimize → phase-fold fixpoint loop — so
|
|
64
|
+
there is no need to assemble one pass at a time to get the CLI's results.
|
|
65
|
+
|
|
66
|
+
```rust,ignore
|
|
67
|
+
use tzap::circuit::Circuit;
|
|
68
|
+
use tzap::optimize::{Options, optimize};
|
|
69
|
+
|
|
70
|
+
let circuit = Circuit::from_qasm(qasm)?;
|
|
71
|
+
let (optimized, report) = optimize(&circuit, &Options::default())?;
|
|
72
|
+
|
|
73
|
+
println!(
|
|
74
|
+
"{} → {} gates, {} → {} T",
|
|
75
|
+
report.baseline.gates, report.output.gates,
|
|
76
|
+
report.baseline.t, report.output.t,
|
|
77
|
+
);
|
|
78
|
+
# Ok::<(), Box<dyn std::error::Error>>(())
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`Options::default()` is the CLI's default: `-O3`, sequential, no Rz or CZ
|
|
82
|
+
decomposition.
|
|
83
|
+
|
|
84
|
+
| Field | Default | Meaning |
|
|
85
|
+
|-------|---------|---------|
|
|
86
|
+
| `level` | `Level::O3` | `O1` (cancel + phase-fold), `O2` (adds SuperOpt, 2 rounds), `O3` (same, to a fixpoint), `Osuper` (`O3` with the bigger SuperOpt bounds) |
|
|
87
|
+
| `passes` | `None` | An explicit `Vec<PassName>` pipeline, replacing `level`'s |
|
|
88
|
+
| `fixpoint` | `false` | Repeat until the gate count stops falling. Only consulted for pipelines that aren't already fixpoint loops (`passes`, or `O1`) |
|
|
89
|
+
| `decompose_rz` | `false` | Decompose Rz into Clifford+T via gridsynth |
|
|
90
|
+
| `decompose_cz` | `false` | Decompose CZ into H+CX+H before optimizing |
|
|
91
|
+
| `rz_epsilon` | `1e-10` | Approximation epsilon for `decompose_rz` |
|
|
92
|
+
| `expr` | `false` | Use the symbolic phase-folding pass. Only consulted under `O1` |
|
|
93
|
+
| `parallel` | `false` | Optimize gate-contiguous chunks concurrently, then concatenate |
|
|
94
|
+
| `superopt` | all `None` | Per-run overrides for the SuperOpt window/table bounds |
|
|
95
|
+
|
|
96
|
+
`Report` carries three sets of `Metrics` (`gates`, `two_qubit`, `depth`, `t`,
|
|
97
|
+
`rz`): `input` as handed in, `baseline` after the eager ccx/ccz (and
|
|
98
|
+
optionally cz) decomposition that precedes optimization, and `output`.
|
|
99
|
+
`baseline` is the honest comparison point for a reduction percentage — it's
|
|
100
|
+
what the optimization passes actually worked against — and equals `input` when
|
|
101
|
+
nothing needed decomposing.
|
|
102
|
+
|
|
103
|
+
### Reporting progress
|
|
104
|
+
|
|
105
|
+
`optimize` is silent. To report progress, implement `Observer` (every method
|
|
106
|
+
defaults to doing nothing) and call `optimize_with`:
|
|
107
|
+
|
|
108
|
+
```rust,ignore
|
|
109
|
+
use tzap::circuit::Circuit;
|
|
110
|
+
use tzap::optimize::{Metrics, Observer, Options, optimize_with};
|
|
111
|
+
|
|
112
|
+
struct Log;
|
|
113
|
+
|
|
114
|
+
impl Observer for Log {
|
|
115
|
+
fn progress_update(&self, round: Option<usize>, current: &Circuit, baseline: Metrics) {
|
|
116
|
+
eprintln!("round {round:?}: {} → {} gates", baseline.gates, current.gates.len());
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let (optimized, _) = optimize_with(&circuit, &Options::default(), &Log)?;
|
|
121
|
+
# Ok::<(), Box<dyn std::error::Error>>(())
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Events fire from whichever thread reaches them, so an `Observer` must be
|
|
125
|
+
`Sync`; under `parallel`, `chunk_done` is called concurrently from rayon
|
|
126
|
+
workers. The chunk workers' own pipelines are always observed by `Silent`,
|
|
127
|
+
since their events would otherwise interleave. Set `tracks_chunks` to `true`
|
|
128
|
+
to receive the `chunks_start`/`chunk_done`/`chunks_end` events — they're
|
|
129
|
+
skipped by default, along with the whole-circuit stitch needed to compute
|
|
130
|
+
their metrics.
|
|
131
|
+
|
|
132
|
+
## Passes
|
|
133
|
+
|
|
134
|
+
The passes below are the building blocks `tzap::optimize` composes. Reach for
|
|
135
|
+
them directly to build a pipeline it doesn't offer.
|
|
136
|
+
|
|
137
|
+
Every pass implements the `Pass` trait:
|
|
138
|
+
|
|
139
|
+
```rust,ignore
|
|
140
|
+
use tzap::pass::Pass;
|
|
141
|
+
|
|
142
|
+
pub trait Pass {
|
|
143
|
+
fn name(&self) -> &str;
|
|
144
|
+
fn run(&self, circuit: &Circuit) -> Circuit;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
A custom pass only needs to supply `name` and `run`.
|
|
149
|
+
|
|
150
|
+
### Available passes
|
|
151
|
+
|
|
152
|
+
| Pass | Import | Description |
|
|
153
|
+
|------|--------|-------------|
|
|
154
|
+
| `DecomposeToffoli` | `tzap::decompose` | Breaks CCX and CCZ gates into Clifford+T |
|
|
155
|
+
| `DecomposeCz` | `tzap::decompose` | Explicitly lowers CZ gates to H+CX+H |
|
|
156
|
+
| `DecomposeRz` | `tzap::decompose` | Decomposes Rz gates into Clifford+T via gridsynth |
|
|
157
|
+
| `CancelGates` | `tzap::cancel` | Removes adjacent self-inverse gate pairs (HH, XX, etc.) |
|
|
158
|
+
| `SuperOpt` | `tzap::super_opt` | Replaces small windows using its shared unitary-to-circuit table |
|
|
159
|
+
| `PhaseFoldRand` | `tzap::phase_fold_rand` | Merges T/Rz gates across the circuit via randomized parity tracking |
|
|
160
|
+
| `PhaseFoldGlobalExpr` | `tzap::phase_fold_global_expr` | Merges T/Rz gates via symbolic parity expressions |
|
|
161
|
+
|
|
162
|
+
### Running passes
|
|
163
|
+
|
|
164
|
+
Run a single pass:
|
|
165
|
+
|
|
166
|
+
```rust,ignore
|
|
167
|
+
use tzap::decompose::DecomposeToffoli;
|
|
168
|
+
|
|
169
|
+
let optimized = DecomposeToffoli.run(&circuit);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Run a pipeline:
|
|
173
|
+
|
|
174
|
+
```rust,ignore
|
|
175
|
+
use tzap::decompose::DecomposeToffoli;
|
|
176
|
+
use tzap::cancel::CancelGates;
|
|
177
|
+
use tzap::phase_fold_rand::PhaseFoldRand;
|
|
178
|
+
use tzap::pass::{Pass, PassResult, run_passes, count_t};
|
|
179
|
+
|
|
180
|
+
let passes: Vec<&dyn Pass> = vec![
|
|
181
|
+
&DecomposeToffoli,
|
|
182
|
+
&CancelGates,
|
|
183
|
+
&PhaseFoldRand,
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
let result: PassResult = run_passes(&circuit, &passes);
|
|
187
|
+
println!("{} gates, {} T", result.circuit.gates.len(), count_t(&result.circuit));
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`run_passes` returns a `PassResult`:
|
|
191
|
+
|
|
192
|
+
```rust,ignore
|
|
193
|
+
pub struct PassResult {
|
|
194
|
+
pub circuit: Circuit,
|
|
195
|
+
pub t_after_first: usize, // T-count after only the first pass
|
|
196
|
+
pub gates_after_first: usize, // gate count after only the first pass
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The `t_after_first` / `gates_after_first` fields are useful for
|
|
201
|
+
attributing reductions to the leading decomposition pass when reporting
|
|
202
|
+
end-to-end numbers. Helpers `count_t` and `count_rz` are also exposed
|
|
203
|
+
from `tzap::pass`.
|
|
204
|
+
|
|
205
|
+
### SuperOpt
|
|
206
|
+
|
|
207
|
+
`SuperOpt` is a peephole pass. It scans each maximal connected subcircuit window
|
|
208
|
+
and replaces it with the smallest equivalent circuit from a precomputed
|
|
209
|
+
unitary-to-circuit table, applying a rewrite only when it strictly reduces the
|
|
210
|
+
gate count. Every replacement is verified by matrix equality up to global phase
|
|
211
|
+
before use, so rewrites are always semantics-preserving. Matrices use exact
|
|
212
|
+
Clifford+T arithmetic; Rz gates act as window barriers and are left unchanged.
|
|
213
|
+
The pass accepts unitary circuits only.
|
|
214
|
+
|
|
215
|
+
```rust,ignore
|
|
216
|
+
use tzap::super_opt::{SuperOpt, SuperOptTableConfig};
|
|
217
|
+
|
|
218
|
+
let pass = SuperOpt::new(3, 10, SuperOptTableConfig::default())?;
|
|
219
|
+
let result = pass.run(&circuit)?;
|
|
220
|
+
println!("{} rewrites", result.rewrites.len());
|
|
221
|
+
# Ok::<(), Box<dyn std::error::Error>>(())
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Parameters:
|
|
225
|
+
|
|
226
|
+
- `max_qubits` — maximum distinct qubits in a scanned window.
|
|
227
|
+
- `window_gates` — maximum gates in a scanned window.
|
|
228
|
+
- `SuperOptTableConfig::new(max_qubits, max_gates, max_entries_per_qubit)` — bounds
|
|
229
|
+
for the synthesis table, independent of the window size; `default()` is
|
|
230
|
+
`(3, 8, 200_000)`. A table entry can only ever be used when it's strictly
|
|
231
|
+
smaller than the window it would replace, so `max_gates` never needs to exceed
|
|
232
|
+
`window_gates - 1`.
|
|
233
|
+
|
|
234
|
+
For a materially more thorough (but slower to build) configuration — the CLI's
|
|
235
|
+
`-Osuper` uses exactly this — try:
|
|
236
|
+
|
|
237
|
+
```rust,ignore
|
|
238
|
+
use tzap::super_opt::{SuperOpt, SuperOptTableConfig};
|
|
239
|
+
|
|
240
|
+
let pass = SuperOpt::new(5, 30, SuperOptTableConfig::new(5, 29, 5_000_000))?;
|
|
241
|
+
# Ok::<(), tzap::super_opt::SuperOptError>(())
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Table construction and caching.** Building the synthesis table is the
|
|
245
|
+
expensive part — breadth-first enumeration over the gate library, bounded by
|
|
246
|
+
`max_gates` and `max_entries_per_qubit`. Tables are cached two ways:
|
|
247
|
+
|
|
248
|
+
1. **Per-process, in-memory.** Every `SuperOpt::new` call with the same
|
|
249
|
+
`SuperOptTableConfig` shares one already-built table for the life of the
|
|
250
|
+
process (`Arc`-backed, keyed by config).
|
|
251
|
+
2. **On disk, across processes.** The built table is also persisted to
|
|
252
|
+
`~/.tzap/superopt-tables/` (one file per distinct config), so a later
|
|
253
|
+
process with the same config loads it in well under a second instead of
|
|
254
|
+
rebuilding it. A missing, stale, or corrupt cache file is never a hard
|
|
255
|
+
error — it just triggers a fresh build, which then gets cached for next
|
|
256
|
+
time. Call `tzap::super_opt::table_is_cached(config)` to check up front
|
|
257
|
+
whether a given config's table is already cached (useful for deciding
|
|
258
|
+
whether to warn a caller that the next `SuperOpt::new` will be slow).
|
|
259
|
+
|
|
260
|
+
`SuperOpt` also implements `tzap::pass::Pass`. Chain `.without_subcircuits()` when
|
|
261
|
+
only the optimized circuit is needed, to skip retaining per-window diagnostics.
|
|
262
|
+
Chain `.incremental()` when repeatedly re-running the same pass instance on
|
|
263
|
+
successive versions of one evolving circuit (e.g. inside a fixpoint loop) — it
|
|
264
|
+
anchors new windows only near what changed since the previous `run` call,
|
|
265
|
+
which is unsound if the instance ever sees unrelated circuits or concurrent
|
|
266
|
+
chunks, so don't share an incremental instance across parallel workers.
|
|
267
|
+
|
|
268
|
+
### DecomposeRz epsilon
|
|
269
|
+
|
|
270
|
+
Control the approximation precision with the `epsilon` field (default `1e-10`):
|
|
271
|
+
|
|
272
|
+
```rust,ignore
|
|
273
|
+
use tzap::decompose::DecomposeRz;
|
|
274
|
+
|
|
275
|
+
let pass = DecomposeRz { epsilon: 1e-6 };
|
|
276
|
+
let cliffordt = pass.run(&circuit);
|
|
277
|
+
```
|