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.
Files changed (56) hide show
  1. tzap-0.4.4/.gitignore +25 -0
  2. tzap-0.4.4/API.md +277 -0
  3. tzap-0.4.4/Cargo.lock +1220 -0
  4. tzap-0.4.4/Cargo.toml +55 -0
  5. tzap-0.4.4/LICENSE +190 -0
  6. tzap-0.4.4/PKG-INFO +177 -0
  7. tzap-0.4.4/README.md +155 -0
  8. tzap-0.4.4/dist-workspace.toml +25 -0
  9. tzap-0.4.4/docs/pennylane.md +166 -0
  10. tzap-0.4.4/docs/qiskit.md +141 -0
  11. tzap-0.4.4/pyproject.toml +58 -0
  12. tzap-0.4.4/python/tzap/__init__.py +23 -0
  13. tzap-0.4.4/python/tzap/__main__.py +3 -0
  14. tzap-0.4.4/python/tzap/_cli.py +72 -0
  15. tzap-0.4.4/python/tzap/_core.py +92 -0
  16. tzap-0.4.4/python/tzap/_native.pyi +35 -0
  17. tzap-0.4.4/python/tzap/pennylane.py +341 -0
  18. tzap-0.4.4/python/tzap/py.typed +1 -0
  19. tzap-0.4.4/python/tzap/qiskit.py +205 -0
  20. tzap-0.4.4/release.toml +9 -0
  21. tzap-0.4.4/src/bench.rs +550 -0
  22. tzap-0.4.4/src/cancel.rs +3115 -0
  23. tzap-0.4.4/src/circuit.rs +553 -0
  24. tzap-0.4.4/src/cli.rs +293 -0
  25. tzap-0.4.4/src/decompose.rs +701 -0
  26. tzap-0.4.4/src/lib.rs +19 -0
  27. tzap-0.4.4/src/main.rs +288 -0
  28. tzap-0.4.4/src/optimize.rs +1121 -0
  29. tzap-0.4.4/src/pass.rs +153 -0
  30. tzap-0.4.4/src/phase_fold_global_expr.rs +663 -0
  31. tzap-0.4.4/src/phase_fold_rand.rs +3193 -0
  32. tzap-0.4.4/src/progress.rs +552 -0
  33. tzap-0.4.4/src/python.rs +173 -0
  34. tzap-0.4.4/src/qasm.rs +2024 -0
  35. tzap-0.4.4/src/super_opt/config.rs +29 -0
  36. tzap-0.4.4/src/super_opt/error.rs +45 -0
  37. tzap-0.4.4/src/super_opt/incremental.rs +107 -0
  38. tzap-0.4.4/src/super_opt/matrix.rs +910 -0
  39. tzap-0.4.4/src/super_opt/matrix_cache.rs +425 -0
  40. tzap-0.4.4/src/super_opt/mod.rs +937 -0
  41. tzap-0.4.4/src/super_opt/synthesis_arena.rs +156 -0
  42. tzap-0.4.4/src/super_opt/table.rs +597 -0
  43. tzap-0.4.4/src/super_opt/tests.rs +2394 -0
  44. tzap-0.4.4/src/unitary.rs +1111 -0
  45. tzap-0.4.4/tests/cli.rs +1675 -0
  46. tzap-0.4.4/tests/cli_errors.rs +956 -0
  47. tzap-0.4.4/tests/fixtures/test.qasm +23 -0
  48. tzap-0.4.4/tests/fixtures/two_ccx.qasm +6 -0
  49. tzap-0.4.4/tests/python/test_bindings.py +359 -0
  50. tzap-0.4.4/tests/python/test_pennylane.py +584 -0
  51. tzap-0.4.4/tests/python/test_python_cli.py +225 -0
  52. tzap-0.4.4/tests/python/test_qiskit.py +382 -0
  53. tzap-0.4.4/tests/python/wheel_smoke.py +36 -0
  54. tzap-0.4.4/tests/qcec.rs +30 -0
  55. tzap-0.4.4/tests/qcec_check.py +93 -0
  56. 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
+ ```