wai-quantum: a rotation's Clifford+T word takes the cheapest unit of the solver's solution, never more T gates than the reference implementation #671
12 changed files with 1583 additions and 49 deletions
18
.github/workflows/wai-quantum-estimate.yml
vendored
18
.github/workflows/wai-quantum-estimate.yml
vendored
|
|
@ -13,7 +13,9 @@ name: wai-quantum resource estimation
|
|||
# (quantum_arch, quantum_bicycle and the estimate job), holds every row to its
|
||||
# published figure (tests/data/published_figures.tsv: the digits it is printed
|
||||
# to, or a guard on ours) and the page docs/published-architectures.md to what
|
||||
# they give. Its search over 4¹¹ Paulis runs in a release build only.
|
||||
# they give. Its search over 4¹¹ Paulis runs in a release build only. So does
|
||||
# the synthesiser's sweep against the reference implementation's T counts
|
||||
# (engine::synth::rz), which every rotation's T count in an estimate comes from.
|
||||
#
|
||||
# In a release build the calibration test measures every point of the
|
||||
# decoders' table again (the rotated surface-code memory experiment, decoded
|
||||
|
|
@ -112,6 +114,20 @@ jobs:
|
|||
grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/published.log" || { echo "::error::$t did not run and pass"; exit 1; }
|
||||
done
|
||||
cargo run --locked --release --example published_architectures --features full
|
||||
- name: Each rotation's Clifford+T word against the reference implementation's T counts
|
||||
shell: bash
|
||||
working-directory: wai/wai-quantum
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cargo test --locked --release --features full --lib -- engine::synth::rz:: 2>&1 | tee "$RUNNER_TEMP/rz.log"
|
||||
grep -qE '^test result: ok\. [1-9][0-9]* passed; 0 failed; 0 ignored' "$RUNNER_TEMP/rz.log" || { echo "::error::the synthesiser's tests did not all run and pass"; exit 1; }
|
||||
for t in engine::synth::rz::tests::never_more_t_gates_than_the_reference_over_the_sweep \
|
||||
engine::synth::rz::tests::the_ising_angles_take_no_more_t_gates_than_the_reference \
|
||||
engine::synth::rz::tests::the_unit_search_takes_no_more_t_gates_and_at_one_count_is_no_farther \
|
||||
engine::synth::rz::tests::fewer_t_gates_can_end_farther_and_one_count_can_take_another_word \
|
||||
engine::synth::rz::tests::the_t_count_is_the_optimum_found_by_exhaustive_search; do
|
||||
grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/rz.log" || { echo "::error::$t did not run and pass"; exit 1; }
|
||||
done
|
||||
- name: The engine without receipts still builds the estimate
|
||||
shell: bash
|
||||
working-directory: wai/wai-quantum
|
||||
|
|
|
|||
25
wai/changes/2026-11-06-rz-fewest-t-unit.md
Normal file
25
wai/changes/2026-11-06-rz-fewest-t-unit.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
### wai-quantum: a rotation's Clifford+T word takes the cheapest unit of the solver's solution
|
||||
|
||||
- `engine::synth::rz` writes, for each solution `u` it finds, the four unit multiples
|
||||
`ωʲ·t` of the solver's solution of the norm equation `t†t = 2ᵏ − |u|²`, and keeps the one
|
||||
whose operator takes the fewest `T` gates; the solver's own unit was taken before. The
|
||||
operators the units give differ by `T` gates and share a diagonal, so for one `u` the word
|
||||
is exactly as near. The equation has other solutions where a prime that splits in `Z[ω]`
|
||||
divides its right side; the solver returns one, and those are not tried.
|
||||
- Which `u` a rotation ends on can change, since one that lost may now win: the fewest `T`
|
||||
gates win, then the nearest. So no rotation takes more `T` gates than before; at an equal
|
||||
`T` count it is no farther; at a lower count it can be farther, and never past its `ε`.
|
||||
Over the 1,192 rows below, 350 words changed, 57 of them at an unchanged `T` count, and 87
|
||||
rotations ended farther from `Rz(θ)`; the Ising program's middle angle at `ε` = 10⁻³ goes
|
||||
from 29 `T` gates at 3.50·10⁻⁴ to 27 at 6.15·10⁻⁴.
|
||||
- Its tests hold the `T` counts to pygridsynth 1.0.1's, the reference implementation, on
|
||||
the Ising program of arXiv:2506.03094 and 128 angles at ε from 10⁻¹ to 10⁻¹³
|
||||
(`tests/data/rz_gridsynth_oracle.csv`, written by
|
||||
`wai/tools/quantum-rz-oracle/gridsynth_oracle.py`, which CI does not run): never more.
|
||||
Before this change 157 of those 1,192 took more.
|
||||
- `T` counts that move: the Ising program's four angles at 5·10⁻⁹, 79, 80, 81, 84 → 79, 80,
|
||||
79, 82 (14,665,600 → 14,592,000 over the program, in the published-architecture check);
|
||||
`rz(1.1)` at 5·10⁻⁵, 40 → 38, and with it the compiler's worked certificate (78 → 76 `T`
|
||||
gates, 200 → 197 gates). A compiled program's output, its hash and the receipts that seal
|
||||
it change wherever one of its rotations takes another word, at an unchanged `T` count
|
||||
too, and a certificate's summed distances can rise with them, never past its budget.
|
||||
79
wai/tools/quantum-rz-oracle/gridsynth_oracle.py
Normal file
79
wai/tools/quantum-rz-oracle/gridsynth_oracle.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env python3
|
||||
"""gridsynth_oracle.py: the reference T counts that the engine's z-rotation
|
||||
synthesiser (wai/wai-quantum/src/engine/synth/rz.rs) is held to.
|
||||
|
||||
For each angle and precision it runs pygridsynth 1.0.1, the reference
|
||||
implementation of the Ross–Selinger algorithm that arXiv:2506.03094 cites, and
|
||||
prints one CSV row: the angle and the precision as the doubles both sides use
|
||||
(Python's repr, which Rust parses back to the same bits), pygridsynth's
|
||||
denominator exponent and its T count. The output is
|
||||
wai/wai-quantum/tests/data/rz_gridsynth_oracle.csv. CI does not run this
|
||||
script and nothing depends on pygridsynth: the CSV is the oracle's answer,
|
||||
committed, and `engine::synth::rz`'s tests read it.
|
||||
|
||||
python3 -m venv /tmp/pgs && /tmp/pgs/bin/pip install "pygridsynth==1.0.1" mpmath
|
||||
/tmp/pgs/bin/python gridsynth_oracle.py > rz_gridsynth_oracle.csv
|
||||
|
||||
The angles:
|
||||
- the four rotation angles of the 10 × 10 Ising program of arXiv:2506.03094
|
||||
(Appendix A.10: a fourth-order product formula, Δ = 0.25, J = g = 1), at
|
||||
its ε = 5·10⁻⁹ and at every precision below;
|
||||
- 128 angles spread over (−π, π) by the golden ratio, θᵢ = −π + 2π·frac(i·φ),
|
||||
none within 10⁻⁶ of a multiple of π/4 (those the engine writes exactly);
|
||||
at ε = 10⁻¹, 10⁻², 10⁻³, 10⁻⁴, 10⁻⁵, 10⁻⁷, 10⁻⁹, 10⁻¹¹ and 10⁻¹³.
|
||||
"""
|
||||
import io
|
||||
import math
|
||||
import multiprocessing
|
||||
import contextlib
|
||||
import sys
|
||||
|
||||
import mpmath
|
||||
from pygridsynth.gridsynth import gridsynth_gates
|
||||
from pygridsynth.gridsynth import gridsynth
|
||||
|
||||
PRECISIONS = [1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-7, 1e-9, 1e-11, 1e-13]
|
||||
|
||||
|
||||
def ising_angles():
|
||||
gamma = 1.0 / (4.0 - 4.0 ** (1.0 / 3.0))
|
||||
delta = 0.25
|
||||
mid = (1.0 - 4.0 * gamma) * delta
|
||||
return [("ising zz", gamma * delta), ("ising x", 2.0 * gamma * delta), ("ising zz middle", mid), ("ising x middle", 2.0 * mid)]
|
||||
|
||||
|
||||
def swept_angles(n=128):
|
||||
phi = (1.0 + math.sqrt(5.0)) / 2.0
|
||||
out = []
|
||||
i = 1
|
||||
while len(out) < n:
|
||||
theta = -math.pi + 2.0 * math.pi * math.fmod(i * phi, 1.0)
|
||||
q = theta / (math.pi / 4.0)
|
||||
if abs(q - round(q)) > 1e-6:
|
||||
out.append((f"sweep {len(out) + 1}", theta))
|
||||
i += 1
|
||||
return out
|
||||
|
||||
|
||||
def run(job):
|
||||
name, theta, eps = job
|
||||
mpmath.mp.dps = 128
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
u = gridsynth(theta=mpmath.mpf(theta), epsilon=mpmath.mpf(eps))
|
||||
gates = gridsynth_gates(theta=mpmath.mpf(theta), epsilon=mpmath.mpf(eps))
|
||||
return (name, repr(theta), repr(eps), u.k, gates.count("T"))
|
||||
|
||||
|
||||
def main():
|
||||
jobs = [(name, theta, 5e-9) for name, theta in ising_angles()]
|
||||
for eps in PRECISIONS:
|
||||
jobs += [(name, theta, eps) for name, theta in ising_angles() + swept_angles()]
|
||||
with multiprocessing.Pool(int(sys.argv[1]) if len(sys.argv) > 1 else 8) as pool:
|
||||
rows = pool.map(run, jobs, chunksize=1)
|
||||
print("name,theta,epsilon,k,t_count")
|
||||
for row in rows:
|
||||
print(",".join(str(x) for x in row))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -58,24 +58,57 @@ pub fn load(given: Option<&Path>, demo: bool) -> Result<Key, String> {
|
|||
None => user_path().ok_or("no HOME to keep a key in: pass --key FILE")?,
|
||||
};
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(text) => {
|
||||
let seed = parse(&text).ok_or_else(|| format!("{}: a key file holds 64 hex digits", path.display()))?;
|
||||
Ok(Key { key: SigningKey::from_bytes(&seed), path: Some(path), made: false })
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound && named.is_none() => {
|
||||
let mut seed = [0u8; 32];
|
||||
getrandom::fill(&mut seed).map_err(|e| format!("no random source for a new key: {e}"))?;
|
||||
if let Some(dir) = path.parent() {
|
||||
std::fs::create_dir_all(dir).map_err(|e| format!("{}: {e}", dir.display()))?;
|
||||
}
|
||||
let text: String = seed.iter().map(|b| format!("{b:02x}")).collect();
|
||||
write_private(&path, &format!("{text}\n")).map_err(|e| format!("{}: {e}", path.display()))?;
|
||||
Ok(Key { key: SigningKey::from_bytes(&seed), path: Some(path), made: true })
|
||||
Ok(text) => key_from(&path, &text),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound && named.is_none() => make(&path),
|
||||
Err(e) => Err(format!("{}: {e}", path.display())),
|
||||
}
|
||||
}
|
||||
|
||||
fn key_from(path: &Path, text: &str) -> Result<Key, String> {
|
||||
let seed = parse(text).ok_or_else(|| format!("{}: a key file holds 64 hex digits", path.display()))?;
|
||||
Ok(Key { key: SigningKey::from_bytes(&seed), path: Some(path.to_path_buf()), made: false })
|
||||
}
|
||||
|
||||
/// This user's key, made at `path` from the operating system's random source.
|
||||
/// Two processes may make it at once, as two agents starting `wai-quantum mcp`
|
||||
/// on a new machine do: the file is never seen partly written, and the one
|
||||
/// that comes second signs with the first's key rather than failing.
|
||||
fn make(path: &Path) -> Result<Key, String> {
|
||||
let mut seed = [0u8; 32];
|
||||
getrandom::fill(&mut seed).map_err(|e| format!("no random source for a new key: {e}"))?;
|
||||
if let Some(dir) = path.parent() {
|
||||
std::fs::create_dir_all(dir).map_err(|e| format!("{}: {e}", dir.display()))?;
|
||||
}
|
||||
let text: String = seed.iter().map(|b| format!("{b:02x}")).collect();
|
||||
match publish_private(path, &format!("{text}\n")) {
|
||||
Ok(()) => Ok(Key { key: SigningKey::from_bytes(&seed), path: Some(path.to_path_buf()), made: true }),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
|
||||
let text = std::fs::read_to_string(path).map_err(|e| format!("{}: {e}", path.display()))?;
|
||||
key_from(path, &text)
|
||||
}
|
||||
Err(e) => Err(format!("{}: {e}", path.display())),
|
||||
}
|
||||
}
|
||||
|
||||
/// `text` at `path`, readable by its owner only and never seen partly
|
||||
/// written: it goes to a new file beside `path`, which is then linked in under
|
||||
/// `path`'s name. The link fails when `path` exists, so of two writers one
|
||||
/// wins and the other is told (`AlreadyExists`). A file system with no links
|
||||
/// takes the text in place, as `write_private` gives it.
|
||||
fn publish_private(path: &Path, text: &str) -> std::io::Result<()> {
|
||||
let mut tag = [0u8; 8];
|
||||
getrandom::fill(&mut tag).map_err(|e| std::io::Error::other(e.to_string()))?;
|
||||
let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("key");
|
||||
let beside = path.with_file_name(format!(".{name}.{}.{:016x}", std::process::id(), u64::from_le_bytes(tag)));
|
||||
write_private(&beside, text)?;
|
||||
let linked = std::fs::hard_link(&beside, path);
|
||||
let _ = std::fs::remove_file(&beside);
|
||||
match linked {
|
||||
Err(e) if e.kind() != std::io::ErrorKind::AlreadyExists => write_private(path, text),
|
||||
done => done,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_private(path: &Path, text: &str) -> std::io::Result<()> {
|
||||
use std::io::Write as _;
|
||||
let mut f = std::fs::OpenOptions::new();
|
||||
|
|
@ -132,4 +165,36 @@ mod tests {
|
|||
}
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Sixteen processes' worth of first uses at once, as when two agents start
|
||||
/// `wai-quantum mcp` on a new machine: each gets a key, all the same one,
|
||||
/// one of them made it, and nothing else is left beside it.
|
||||
#[test]
|
||||
fn keys_made_at_once_are_one_key() {
|
||||
let dir = std::env::temp_dir().join(format!("wai-quantum-cli-key-race-{}", std::process::id()));
|
||||
let path = dir.join("wai").join("quantum-key");
|
||||
for round in 0..25 {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let start = std::sync::Arc::new(std::sync::Barrier::new(16));
|
||||
let threads: Vec<_> = (0..16)
|
||||
.map(|_| {
|
||||
let (path, start) = (path.clone(), start.clone());
|
||||
std::thread::spawn(move || {
|
||||
start.wait();
|
||||
make(&path).map(|k| (k.key.to_bytes(), k.made))
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let got: Vec<([u8; 32], bool)> = threads.into_iter().map(|t| t.join().unwrap()).collect::<Result<_, _>>().unwrap_or_else(|e| panic!("round {round}: {e}"));
|
||||
assert!(got.windows(2).all(|w| w[0].0 == w[1].0), "round {round}: more than one key");
|
||||
assert_eq!(got.iter().filter(|g| g.1).count(), 1, "round {round}: one of them made it");
|
||||
assert_eq!(std::fs::read_dir(path.parent().unwrap()).unwrap().count(), 1, "round {round}: only the key is left");
|
||||
assert_eq!(load_from_file(&path), got[0].0, "round {round}: the file holds that key");
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
fn load_from_file(path: &Path) -> [u8; 32] {
|
||||
key_from(path, &std::fs::read_to_string(path).unwrap()).unwrap().key.to_bytes()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,14 +91,17 @@ fn json_output_is_json_on_every_kind_of_job() {
|
|||
|
||||
/// One request to the MCP server on standard input and output.
|
||||
fn mcp(tool: &str, arguments: Value) -> Value {
|
||||
let mut child = Command::new(BIN).arg("mcp").env_remove("WAI_QUANTUM_KEY").stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::null()).spawn().unwrap();
|
||||
let mut child = Command::new(BIN).arg("mcp").env_remove("WAI_QUANTUM_KEY").stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().unwrap();
|
||||
let mut stdin = child.stdin.take().unwrap();
|
||||
writeln!(stdin, "{}", json!({ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": { "name": "test", "version": "0" } } })).unwrap();
|
||||
writeln!(stdin, "{}", json!({ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": tool, "arguments": arguments } })).unwrap();
|
||||
drop(stdin);
|
||||
let out = child.wait_with_output().unwrap();
|
||||
let text = String::from_utf8_lossy(&out.stdout);
|
||||
let answer: Value = text.lines().filter_map(|l| serde_json::from_str::<Value>(l).ok()).find(|v| v["id"] == 2).unwrap_or_else(|| panic!("{text}"));
|
||||
// A server that answers nothing says why on its standard error, which the panic keeps.
|
||||
let answer: Value = text.lines().filter_map(|l| serde_json::from_str::<Value>(l).ok()).find(|v| v["id"] == 2).unwrap_or_else(|| {
|
||||
panic!("{tool}: no answer from the server ({}): standard output {text:?}, standard error {:?}", out.status, String::from_utf8_lossy(&out.stderr))
|
||||
});
|
||||
answer["result"]["structuredContent"].clone()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,8 +104,8 @@ For `h q[0]; rz(0.3) q[0]; cx q[0], q[1]; ry(1.1) q[1];` at `--epsilon 1e-4`
|
|||
"level": 1,
|
||||
"source": { "hash": "3088646d…", "qubits": 2,
|
||||
"counts": { "gates": 4, "cnots": 1, "t_count": 0, "rotations": 2, "depth": 4, "…": "…" } },
|
||||
"output": { "hash": "65e0c256…",
|
||||
"counts": { "gates": 200, "cnots": 1, "t_count": 78, "rotations": 0, "depth": 200, "…": "…" } },
|
||||
"output": { "hash": "a6aff9b0…",
|
||||
"counts": { "gates": 197, "cnots": 1, "t_count": 76, "rotations": 0, "depth": 197, "…": "…" } },
|
||||
"epsilon": { "budget": 0.0001, "rotations": 2, "each": 5e-05, "bound": 7.453429548248133e-05 },
|
||||
"passes": [
|
||||
{ "pass": "simplify", "before": { "…": "…" }, "after": { "…": "…" },
|
||||
|
|
@ -114,16 +114,16 @@ For `h q[0]; rz(0.3) q[0]; cx q[0], q[1]; ry(1.1) q[1];` at `--epsilon 1e-4`
|
|||
"check": { "how": "each kind of gate against its decomposition, on its own qubits", "verdict": "exact", "checked": 1, "distance": 5.551115123125783e-17 } },
|
||||
{ "pass": "approximate", "…": "…",
|
||||
"check": { "how": "each rotation's Clifford+T word multiplied out in exact ring arithmetic, and its operator-norm distance to the rotation recomputed from the word in fixed point and rounded up",
|
||||
"verdict": "approximate", "rotations": 2, "epsilon_each": 5e-05, "t_count": 78, "distance": 7.453429548248133e-05 } },
|
||||
"verdict": "approximate", "rotations": 2, "epsilon_each": 5e-05, "t_count": 76, "distance": 7.453429548248133e-05 } },
|
||||
{ "pass": "phase_fold", "…": "…",
|
||||
"check": { "how": "the phase polynomial recomputed: … in whole eighths of a turn", "verdict": "proof", "merged": 41 } },
|
||||
"check": { "how": "the phase polynomial recomputed: … in whole eighths of a turn", "verdict": "proof", "merged": 39 } },
|
||||
{ "…": "…" }
|
||||
],
|
||||
"rotations": [
|
||||
{ "angle": 0.3, "t_count": 38, "distance": 3.77439802702028e-05, "k": 20 },
|
||||
{ "angle": 1.1, "t_count": 40, "distance": 3.679031521227853e-05, "k": 20 }
|
||||
{ "angle": 1.1, "t_count": 38, "distance": 3.679031521227853e-05, "k": 20 }
|
||||
],
|
||||
"whole": { "how": "the dense unitaries of the whole program and the whole output", "distance": 7.123665880156653e-05, "verdict": "approximate" },
|
||||
"whole": { "how": "the dense unitaries of the whole program and the whole output", "distance": 7.269652454265581e-05, "verdict": "approximate" },
|
||||
"verdict": "approximate",
|
||||
"reading": "The output equals the program up to a global phase to within 7.453429548248133e-5 in the operator norm (the budget was 1e-4). …"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,9 @@ Ising program. Its other differences are explained in their rows:
|
|||
capability at 10⁻³ (its own row).
|
||||
- Its Ising program takes 15,542,400 T gates in its tables and 19,036,800 in
|
||||
its text and Fig. 1. pygridsynth 1.0.1, the version it cites, gives the
|
||||
15,542,400 exactly.
|
||||
15,542,400 exactly. Our synthesiser also searches the rotations one global
|
||||
phase away and takes 6.1% fewer, and no more than pygridsynth on any of the
|
||||
angles and precisions its tests compare.
|
||||
- Its gross Ising machine of 4,817 qubits takes the adapter of the 10⁻³
|
||||
factory beside the 10⁻⁴ factory it names (4,801).
|
||||
- The Ising program's inter-module measurements match its 946,800 with the
|
||||
|
|
@ -268,8 +270,8 @@ its own formula, and so on. Its Toffoli count comes back.
|
|||
| in-module measurements `N_M`, two-gross | arXiv:2506.03094 Table 9, source files | 4,238,828 | 4,148,800 | -2.12% | our limit | Ours removes no repeated in-module measurement, which theirs does (§4), and takes sites in order, which it does not print. Its counts also carry the compile section's action of a shift on Z̄, which puts its gross below its two-gross; ours has them the other way round. |
|
||||
| automorphisms `N_U`, gross | arXiv:2506.03094 Table 9, source files | 2,270,400 | 2,728,800 | +20.19% | our limit | Ours takes the fewest shifts on a path of fewest measurements, and merges none; theirs is a compiler's. |
|
||||
| automorphisms `N_U`, two-gross | arXiv:2506.03094 Table 9, source files | 2,294,400 | 2,368,000 | +3.21% | our limit | Ours takes the fewest shifts on a path of fewest measurements, and merges none; theirs is a compiler's. |
|
||||
| T injections `N_T` | arXiv:2506.03094 Table 9, source files | 15,542,400 | 14,665,600 | -5.64% | their model | pygridsynth 1.0.1, the version the source cites, run on the program's four angles at ε = 5·10⁻⁹, gives 86, 80, 84 and 82 T gates, which make 15,542,400 exactly; it approximates the rotation itself. Ours, the same Ross–Selinger method, gives 79, 80, 81 and 84, and also searches the class one global phase e^(iπ/8) away, which a rotation of the pivot allows. |
|
||||
| T injections as §4 and Fig. 1(b) state | arXiv:2506.03094 §4; Fig. 1(b), source files | 1.9·10⁷ (Fig. 1(b): 19,036,800) | 14,665,600 | -22.96% | their numbers | §4 says N_T = 1.9·10⁷ and Fig. 1(b) plots 19,036,800. Table 9 and every failure probability of Table 10 take 15,542,400, which the row above compares ours with. |
|
||||
| T injections `N_T` | arXiv:2506.03094 Table 9, source files | 15,542,400 | 14,592,000 | -6.11% | their model | pygridsynth 1.0.1, the version the source cites, run on the program's four angles at ε = 5·10⁻⁹, gives 86, 80, 84 and 82 T gates, which make 15,542,400 exactly; it approximates the rotation itself. Ours, the same Ross–Selinger method, gives 79, 80, 79 and 82: it also searches the class one global phase e^(iπ/8) away, which a rotation of the pivot allows, and of the solver's solution of the norm equation and its unit multiples it keeps the one whose operator takes the fewest T gates. It takes no more than the reference on any of 1,192 angles and precisions (engine::synth::rz's tests). |
|
||||
| T injections as §4 and Fig. 1(b) state | arXiv:2506.03094 §4; Fig. 1(b), source files | 1.9·10⁷ (Fig. 1(b): 19,036,800) | 14,592,000 | -23.35% | their numbers | §4 says N_T = 1.9·10⁷ and Fig. 1(b) plots 19,036,800. Table 9 and every failure probability of Table 10 take 15,542,400, which the row above compares ours with. |
|
||||
| idles `N_I`, two-gross, from its `N_T` | arXiv:2506.03094 Table 9, source files | 41,636,714,298 | 42,100,476,000 | +1.11% | our limit | Ours: every module idles while the factory works, and otherwise never. Theirs counts the idles of its schedule. |
|
||||
| failure probability, gross, p = 10⁻³, its counts | arXiv:2506.03094 Table 10 | 3.33·10⁴ | 3.331e4 | +0.03% | agrees | Table 9's counts at our evaluation of Table 6's fits (Eq. (26)); the band moves the fits' parameters by their last printed digit. |
|
||||
| failure probability, gross, p = 10⁻⁴, its counts | arXiv:2506.03094 Table 10 | 7.97·10⁻¹ | 7.977e-1 | +0.09% | agrees | Table 9's counts at our evaluation of Table 6's fits (Eq. (26)); the band moves the fits' parameters by their last printed digit. |
|
||||
|
|
|
|||
|
|
@ -19,31 +19,41 @@ fn hx(b: &[u8]) -> String {
|
|||
/// (`formats::jeff`), so that column moves with every release of wai-quantum:
|
||||
/// taken at 0.8.1, it was written again at 0.9.0 from the engine's own export,
|
||||
/// which at 0.8.1 still gives the values taken then.
|
||||
///
|
||||
/// Since the synthesiser keeps the cheapest unit multiple of the norm
|
||||
/// equation's solution (`engine::synth::rz`), a rotation's word can take fewer
|
||||
/// `T` gates, so the compile's output and the estimate's counts of the rows
|
||||
/// with such rotations are what it gives now, every one with fewer `T` gates:
|
||||
/// the compile of c3z 1080 → 1059, c3x 1080 → 1061, c3y 1079 → 1063, c3h
|
||||
/// 1081 → 1065, c2u 1066 → 1058, c2sx 483 → 476, ccu 1078 → 1065, c2swap
|
||||
/// 1080 → 1061 and grover6 10080 → 9828; the estimate's rotation `T` of c2rx,
|
||||
/// c2ry and c2rzz 460 → 450, c2u 1647 → 1631, c2sx 624 → 611, ccu 1807 →
|
||||
/// 1791, c2swap 720 → 705, c2gphase 117 → 114 and cp 114 → 108.
|
||||
#[rustfmt::skip]
|
||||
#[allow(clippy::type_complexity)]
|
||||
const BEFORE: &[(&str, &str, usize, &str, &str, &str, &str, &str, &str)] = &[
|
||||
("c3z", "qubit[5] q;\nh q;\nctrl(3) @ z q[0], q[1], q[2], q[3];\n", 34, "29718f9ab500304a", "181acaa4aad01230", "f23087907c80c69d", "c8c5157978bce0e3", "65bc71cbed38d1a8", "747ea02fde8aeaca"),
|
||||
("c3x", "qubit[5] q;\nh q;\nctrl(3) @ x q[0], q[1], q[2], q[3];\n", 36, "7a786cc271f2501b", "8a504be7fc0299e8", "19be6f5bfc085d84", "0c304a3bc21ab20d", "daf3f611f19ea19a", "d7c18a0142c206a0"),
|
||||
("c3y", "qubit[5] q;\nh q;\nt q[1];\nctrl(3) @ y q[0], q[1], q[2], q[3];\n", 39, "83dfa98a747a790d", "fbc90dbfada344a4", "263ae5be1f8ed271", "3248786655ef4b22", "2e406a5884783142", "9fd8aa3d94545ea2"),
|
||||
("c3h", "qubit[5] q;\nh q;\nt q[1];\nctrl(3) @ h q[0], q[1], q[2], q[3];\n", 37, "d6da69225d63f4a2", "f9fd9fa5547c7200", "de15875954d42157", "b2d92db2f0938d25", "5537e974bf7a9789", "744da0841af0f928"),
|
||||
("c3z", "qubit[5] q;\nh q;\nctrl(3) @ z q[0], q[1], q[2], q[3];\n", 34, "29718f9ab500304a", "181acaa4aad01230", "d6ac65a5d000432d", "c8c5157978bce0e3", "65bc71cbed38d1a8", "747ea02fde8aeaca"),
|
||||
("c3x", "qubit[5] q;\nh q;\nctrl(3) @ x q[0], q[1], q[2], q[3];\n", 36, "7a786cc271f2501b", "8a504be7fc0299e8", "d67b92d5f3fdb9cf", "0c304a3bc21ab20d", "daf3f611f19ea19a", "d7c18a0142c206a0"),
|
||||
("c3y", "qubit[5] q;\nh q;\nt q[1];\nctrl(3) @ y q[0], q[1], q[2], q[3];\n", 39, "83dfa98a747a790d", "fbc90dbfada344a4", "55221950a66c19b9", "3248786655ef4b22", "2e406a5884783142", "9fd8aa3d94545ea2"),
|
||||
("c3h", "qubit[5] q;\nh q;\nt q[1];\nctrl(3) @ h q[0], q[1], q[2], q[3];\n", 37, "d6da69225d63f4a2", "f9fd9fa5547c7200", "0c3cd36f5611ac6d", "b2d92db2f0938d25", "5537e974bf7a9789", "744da0841af0f928"),
|
||||
("c2p", "qubit[4] q;\nh q;\nctrl(2) @ p(pi / 8) q[0], q[1], q[2];\n", 17, "b076ca7327b88b22", "13346662e1739d87", "18407afca6242403", "82518b8435656332", "fac0bb93925ec89d", "0da1476c3de0dffa"),
|
||||
("c2rz", "qubit[4] q;\nh q;\nctrl(2) @ rz(pi / 4) q[0], q[1], q[2];\n", 18, "ade98300c7a91bea", "ca049c0c8dd637ed", "a4f6414c00826c92", "0f6065ef438dadba", "967d0020dc595ca1", "d2f07d8d123fa85a"),
|
||||
("c2rx", "qubit[4] q;\nh q;\nctrl(2) @ rx(pi / 2) q[0], q[1], q[2];\n", 20, "6ae6ae2309534fbb", "3eab347bbdb8b27c", "5a664df2c0c8b771", "4dc76727674b23fa", "c938f52de02329e6", "d992c105de67f83c"),
|
||||
("c2ry", "qubit[4] q;\nh q;\nctrl(2) @ ry(pi / 2) q[0], q[1], q[2];\n", 22, "27369425f1ce33f2", "94f7f25bde25caef", "947dd07eb6275558", "27aeedd8797a64c3", "baccb437d38345c8", "b75ea5448c736963"),
|
||||
("c2u", "qubit[4] q;\nh q;\nctrl(2) @ u(pi / 2, pi / 4, pi / 8) q[0], q[1], q[2];\n", 51, "0032cafc1cf71c51", "7c43ae072ebd46be", "e8f8bdd2fd064d70", "529d746a02266c75", "2bfcec998fd6fbc9", "94c720252c873ede"),
|
||||
("c2sx", "qubit[4] q;\nh q;\nctrl(2) @ sx q[0], q[1], q[2];\n", 21, "3011642a37dab1c8", "378d6c6c3635da01", "e65547fe84b0d8cf", "8a53373f168fcddd", "45331d3739d29283", "c3e8535f252e0170"),
|
||||
("c2rx", "qubit[4] q;\nh q;\nctrl(2) @ rx(pi / 2) q[0], q[1], q[2];\n", 20, "6ae6ae2309534fbb", "3eab347bbdb8b27c", "5a664df2c0c8b771", "4dc76727674b23fa", "c938f52de02329e6", "04e354bedaac05f0"),
|
||||
("c2ry", "qubit[4] q;\nh q;\nctrl(2) @ ry(pi / 2) q[0], q[1], q[2];\n", 22, "27369425f1ce33f2", "94f7f25bde25caef", "947dd07eb6275558", "27aeedd8797a64c3", "baccb437d38345c8", "d06687536842afbb"),
|
||||
("c2u", "qubit[4] q;\nh q;\nctrl(2) @ u(pi / 2, pi / 4, pi / 8) q[0], q[1], q[2];\n", 51, "0032cafc1cf71c51", "7c43ae072ebd46be", "528d9ce71320056f", "529d746a02266c75", "2bfcec998fd6fbc9", "d52535001b3fd082"),
|
||||
("c2sx", "qubit[4] q;\nh q;\nctrl(2) @ sx q[0], q[1], q[2];\n", 21, "3011642a37dab1c8", "378d6c6c3635da01", "8054090c33152d4e", "8a53373f168fcddd", "45331d3739d29283", "f1b22819c6f6d640"),
|
||||
("neg", "qubit[4] q;\nh q;\nnegctrl(2) @ ctrl @ s q[0], q[1], q[2], q[3];\n", 37, "64475caf52138761", "77667386517f1f09", "1c755ad6249197a1", "e46c16636e80eebd", "ae0ea2e1a600fe8c", "e42f39af5237beca"),
|
||||
("ccu", "qubit[4] q;\nh q;\nctrl @ cu(pi / 2, pi / 4, pi / 8, pi / 16) q[0], q[1], q[2];\n", 52, "a9f7955f0864db33", "fa538ea89cfe5c9a", "0ba490c2dda2780c", "c40db1252b470677", "9af7de5cf84e3975", "b27c6c0bb9082d58"),
|
||||
("c2swap", "qubit[4] q;\nh q;\nctrl(2) @ swap q[0], q[1], q[2], q[3];\n", 37, "90d28cfbf2beef6c", "7982dfa1bd563330", "76fee435a36b077d", "f1554f2143fd5485", "3eac44e71c6e6884", "fe16b8d3921fa66b"),
|
||||
("c2rzz", "qubit[4] q;\nh q;\nctrl(2) @ rzz(pi / 2) q[0], q[1], q[2], q[3];\n", 20, "169448464ac572eb", "b537afe5c235346c", "c0df4fdb5cb39f1a", "e7c473ac7aced773", "7b9403a68c4191c1", "d992c105de67f83c"),
|
||||
("c2gphase", "qubit[3] q;\nh q;\nctrl(2) @ gphase(pi / 4) q[0], q[1];\n", 4, "613a1f7f53c2c634", "0e065cabc1032cc6", "eb75b70b655bedca", "87c33623c70c4586", "2f5443784c488a78", "709224f43ac6c30e"),
|
||||
("ccu", "qubit[4] q;\nh q;\nctrl @ cu(pi / 2, pi / 4, pi / 8, pi / 16) q[0], q[1], q[2];\n", 52, "a9f7955f0864db33", "fa538ea89cfe5c9a", "4cddec462306adfb", "c40db1252b470677", "9af7de5cf84e3975", "2cb9eefc64e918ec"),
|
||||
("c2swap", "qubit[4] q;\nh q;\nctrl(2) @ swap q[0], q[1], q[2], q[3];\n", 37, "90d28cfbf2beef6c", "7982dfa1bd563330", "827673a89608715b", "f1554f2143fd5485", "3eac44e71c6e6884", "869908729c2e89a4"),
|
||||
("c2rzz", "qubit[4] q;\nh q;\nctrl(2) @ rzz(pi / 2) q[0], q[1], q[2], q[3];\n", 20, "169448464ac572eb", "b537afe5c235346c", "c0df4fdb5cb39f1a", "e7c473ac7aced773", "7b9403a68c4191c1", "04e354bedaac05f0"),
|
||||
("c2gphase", "qubit[3] q;\nh q;\nctrl(2) @ gphase(pi / 4) q[0], q[1];\n", 4, "613a1f7f53c2c634", "0e065cabc1032cc6", "eb75b70b655bedca", "87c33623c70c4586", "2f5443784c488a78", "f161950ccab25f70"),
|
||||
("invc3t", "qubit[4] q;\nh q;\ninv @ ctrl(3) @ t q[0], q[1], q[2], q[3];\n", 33, "6d8735fa38b73015", "8ac349a04bd30ca8", "389140fbf3231c9f", "cfc33faa8c2f6e40", "2483f0a54b74abf8", "b0d2a886a160dcc2"),
|
||||
("grover6", "qubit[6] q;\nh q;\nctrl(5) @ z q[0], q[1], q[2], q[3], q[4], q[5];\nh q;\nx q;\nctrl(5) @ z q[0], q[1], q[2], q[3], q[4], q[5];\nx q;\nh q;\n", 280, "71390b8e8200400a", "9e5c239c034ba374", "9b7fb2311b984049", "384c13c05764b82d", "b31d63424bc43e4e", "d164abfb32a62b0a"),
|
||||
("grover6", "qubit[6] q;\nh q;\nctrl(5) @ z q[0], q[1], q[2], q[3], q[4], q[5];\nh q;\nx q;\nctrl(5) @ z q[0], q[1], q[2], q[3], q[4], q[5];\nx q;\nh q;\n", 280, "71390b8e8200400a", "9e5c239c034ba374", "a14f0c960297a237", "384c13c05764b82d", "b31d63424bc43e4e", "d164abfb32a62b0a"),
|
||||
("ccx", "qubit[4] q;\nh q;\nt q[0];\nctrl(2) @ x q[0], q[1], q[2];\n", 6, "542e1352d6cd7c2e", "bc16012d7e5c98a8", "99b450e817595855", "6645931b4b759afb", "3bb1330f3ba2a9d6", "d370a78cc7e92248"),
|
||||
("ccz", "qubit[4] q;\nh q;\nt q[0];\nctrl(2) @ z q[0], q[1], q[2];\n", 6, "32cc8ef799a35dc2", "b357eae18a404904", "f3b4c56a4b8d0c9d", "7a9c51e72904ac4a", "580547bf20c7c3e4", "d370a78cc7e92248"),
|
||||
("c2y", "qubit[4] q;\nh q;\nt q[0];\nctrl(2) @ y q[0], q[1], q[2];\n", 8, "2c326695e1bde126", "59d46a5019e4d87c", "dacf9a1398e3e043", "a9ff98ce426f905d", "5e943acf949d0ebd", "551165238caa42d2"),
|
||||
("c2h", "qubit[4] q;\nh q;\nt q[0];\nctrl(2) @ h q[0], q[1], q[2];\n", 8, "ae920e55c106966c", "e61608a2117a83aa", "58443259e04d5f16", "7e9d2678d90ccadc", "f17c81d581143ea0", "d76ade781f528b10"),
|
||||
("cp", "qubit[3] q;\nh q;\nctrl @ p(pi / 8) q[0], q[1];\n", 4, "48ca258d13245d56", "cddd783aad2071d2", "e062f8754960a2d9", "ed65712144049bea", "9293396e5fad806a", "0e28272539b04b2a"),
|
||||
("cp", "qubit[3] q;\nh q;\nctrl @ p(pi / 8) q[0], q[1];\n", 4, "48ca258d13245d56", "cddd783aad2071d2", "e062f8754960a2d9", "ed65712144049bea", "9293396e5fad806a", "ce8fbc7d4e31cb46"),
|
||||
("cswap", "qubit[3] q;\nh q;\nctrl @ swap q[0], q[1], q[2];\n", 4, "2a5e8a545c769f54", "70fe25ec22008f86", "27282765b5abf030", "e85d174f9e4e97da", "adc56410e186e7cf", "694084d5478b13ed"),
|
||||
("plain", "qubit[3] q;\nh q;\ncx q[0], q[1];\nrz(pi / 4) q[2];\n", 5, "8103a71b8f08fb58", "92dbd1cf0f4ce0e1", "275c3c9040292ce0", "2bd714b375f08ed0", "35d1172993ba3e67", "68eebfa353685fb7"),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -14,7 +14,23 @@
|
|||
//! grid problem ([`super::grid`]), each tested exactly; for each, `t†t =
|
||||
//! 2ᵏ − |u|²` is solved ([`super::dioph`]); the first `k` with a solution
|
||||
//! gives the operator, which [`super::ring::synthesize`] writes as the word
|
||||
//! with the fewest `T` gates. The `T` count is about `3·log₂(1/ε)`.
|
||||
//! with the fewest `T` gates. Every unit multiple `ωʲ·t` of the solver's `t`
|
||||
//! solves the equation too, and the operators they give differ by `T` gates,
|
||||
//! so each is written and the fewest kept. (The equation has other solutions
|
||||
//! where a prime that splits in `Z[ω]` divides `2ᵏ − |u|²`; the solver returns
|
||||
//! one, and those are not tried.) The `T` count is about `3·log₂(1/ε)`.
|
||||
//!
|
||||
//! Of the words within `ε` at that `k`, the fewest `T` gates win, then the
|
||||
//! nearest. So what the unit search does to a rotation is this: it never takes
|
||||
//! more `T` gates than the solver's own unit would; at an equal count it is no
|
||||
//! farther; at a lower count it can be farther, because a `u` that lost may now
|
||||
//! win with its own distance, but never past `ε`. Its word can change at an
|
||||
//! equal count too.
|
||||
//!
|
||||
//! The tests hold the `T` counts to pygridsynth 1.0.1's, the reference
|
||||
//! implementation, on 1,192 angles and precisions
|
||||
//! (`tests/data/rz_gridsynth_oracle.csv`, written by
|
||||
//! `wai/tools/quantum-rz-oracle/gridsynth_oracle.py`): never more.
|
||||
//!
|
||||
//! What is returned has been checked twice over: the word multiplies back,
|
||||
//! exactly, to the operator found, and its distance to `Rz(θ)` is computed
|
||||
|
|
@ -84,6 +100,30 @@ pub fn word_distance(gates: &[Kind], theta: f64) -> f64 {
|
|||
d + d * 1e-15 + f64::MIN_POSITIVE
|
||||
}
|
||||
|
||||
/// The operator `(1/√2ᵏ)·[[u, −t'†ωˡ], [t', u†ωˡ]]` with the fewest `T` gates,
|
||||
/// over the unit multiples `t' = ωʲ·t` of the solver's solution of the norm
|
||||
/// equation, as a word.
|
||||
///
|
||||
/// Each `ωʲ·t` solves `t†t = 2ᵏ − |u|²`, and the operators they give differ by
|
||||
/// `T` gates: `t' = ω·t` is `T·U·T†` with `ω` taken into the second column's
|
||||
/// phase. Their diagonals are the same, so for this `u` each is as near
|
||||
/// `Rz(θ)`. `j` and `j + 4` give operators conjugate by `Z`, a Clifford, with
|
||||
/// one `T` count, so `j < 4` covers them. The solver's own `t` comes first and
|
||||
/// is kept on a tie.
|
||||
fn fewest_t_completion(u: &Zw, t: &Zw, l: i64, k: u32) -> Result<super::ring::Word, String> {
|
||||
let wl = Zw::omega(l);
|
||||
let mut best: Option<super::ring::Word> = None;
|
||||
for j in 0..4 {
|
||||
let tj = t.mul_omega(j);
|
||||
let op = Mat2::new([u.clone(), tj.adj().mul(&wl).neg(), tj, u.adj().mul(&wl)], k);
|
||||
let word = synthesize(&op)?;
|
||||
if best.as_ref().is_none_or(|b| word.t_count < b.t_count) {
|
||||
best = Some(word);
|
||||
}
|
||||
}
|
||||
Ok(best.expect("four completions were written"))
|
||||
}
|
||||
|
||||
/// The word for `Rz(mπ/4)` up to a phase: `T^m`.
|
||||
fn quarter_word(m: i64) -> Vec<Kind> {
|
||||
match m.rem_euclid(8) {
|
||||
|
|
@ -102,6 +142,11 @@ fn quarter_word(m: i64) -> Vec<Kind> {
|
|||
/// search finds at the least denominator exponent; refused for `ε` outside
|
||||
/// [`MIN_EPSILON`, `MAX_EPSILON`].
|
||||
pub fn rz(theta: f64, epsilon: f64) -> Result<Rotation, String> {
|
||||
rz_by(theta, epsilon, fewest_t_completion)
|
||||
}
|
||||
|
||||
/// [`rz`], each `u`'s operator written by `complete`.
|
||||
fn rz_by(theta: f64, epsilon: f64, complete: fn(&Zw, &Zw, i64, u32) -> Result<super::ring::Word, String>) -> Result<Rotation, String> {
|
||||
if !theta.is_finite() {
|
||||
return Err(format!("the angle {theta} is not a number"));
|
||||
}
|
||||
|
|
@ -165,9 +210,7 @@ pub fn rz(theta: f64, epsilon: f64) -> Result<Rotation, String> {
|
|||
equations += 1;
|
||||
let mut budget = FACTOR_BUDGET;
|
||||
let Some(t) = dioph::solve(&xi, &mut budget) else { continue };
|
||||
let wl = Zw::omega(*l);
|
||||
let op = Mat2::new([u.clone(), t.adj().mul(&wl).neg(), t, u.adj().mul(&wl)], k);
|
||||
let word = synthesize(&op)?;
|
||||
let word = complete(u, &t, *l, k)?;
|
||||
let distance = word_distance(&word.gates, theta);
|
||||
if distance <= epsilon {
|
||||
found.push((word.gates, word.t_count, distance));
|
||||
|
|
@ -340,4 +383,102 @@ mod tests {
|
|||
assert!(rz(0.3, 0.6).is_err());
|
||||
assert!(rz(f64::NAN, 1e-3).is_err());
|
||||
}
|
||||
|
||||
/// The reference T counts: pygridsynth 1.0.1, run by
|
||||
/// `wai/tools/quantum-rz-oracle/gridsynth_oracle.py` on the four angles of
|
||||
/// the Ising program of arXiv:2506.03094 and on 128 angles over (−π, π),
|
||||
/// at ε from 10⁻¹ to 10⁻¹³. Rows: name, θ, ε, its exponent, its T count.
|
||||
fn oracle() -> Vec<(String, f64, f64, usize)> {
|
||||
include_str!("../../../tests/data/rz_gridsynth_oracle.csv")
|
||||
.lines()
|
||||
.skip(1)
|
||||
.map(|line| {
|
||||
let f: Vec<&str> = line.split(',').collect();
|
||||
(f[0].to_string(), f[1].parse().unwrap(), f[2].parse().unwrap(), f[4].parse().unwrap())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_ising_angles_take_no_more_t_gates_than_the_reference() {
|
||||
// At ε = 5·10⁻⁹ the reference takes 86, 80, 84 and 82: 15,542,400 over
|
||||
// the program. Here the third took 81 and the fourth 84 while the
|
||||
// solver's own unit was taken.
|
||||
let rows: Vec<_> = oracle().into_iter().filter(|r| r.0.starts_with("ising") && r.2 == 5e-9).collect();
|
||||
assert_eq!(rows.iter().map(|r| r.3).collect::<Vec<_>>(), [86, 80, 84, 82]);
|
||||
let ours: Vec<usize> = rows.iter().map(|r| rz(r.1, r.2).unwrap().t_count).collect();
|
||||
for (r, t) in rows.iter().zip(&ours) {
|
||||
assert!(*t <= r.3, "{}: {t} T gates, the reference {}", r.0, r.3);
|
||||
}
|
||||
assert_eq!(ours, [79, 80, 79, 82]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(debug_assertions, ignore = "about twelve hundred syntheses: release only")]
|
||||
fn never_more_t_gates_than_the_reference_over_the_sweep() {
|
||||
let rows = oracle();
|
||||
assert_eq!(rows.len(), 1192);
|
||||
let (mut fewer, mut equal, mut saved) = (0, 0, 0);
|
||||
let mut worse = Vec::new();
|
||||
for (name, theta, eps, reference) in &rows {
|
||||
let r = rz(*theta, *eps).unwrap();
|
||||
assert!(r.distance <= *eps, "{name}, ε {eps}: distance {}", r.distance);
|
||||
match r.t_count.cmp(reference) {
|
||||
std::cmp::Ordering::Less => {
|
||||
fewer += 1;
|
||||
saved += reference - r.t_count;
|
||||
}
|
||||
std::cmp::Ordering::Equal => equal += 1,
|
||||
std::cmp::Ordering::Greater => worse.push(format!("{name} θ {theta} ε {eps}: {} against {reference}", r.t_count)),
|
||||
}
|
||||
}
|
||||
eprintln!("against the reference: {fewer} fewer ({saved} T gates in all), {equal} equal, {} more", worse.len());
|
||||
assert!(worse.is_empty(), "{} rows take more T gates than the reference: {worse:#?}", worse.len());
|
||||
}
|
||||
|
||||
/// The operator as the solver's own `t` gives it, as [`rz`] once took it.
|
||||
fn the_solver_s_own_unit(u: &Zw, t: &Zw, l: i64, k: u32) -> Result<crate::engine::synth::ring::Word, String> {
|
||||
let wl = Zw::omega(l);
|
||||
synthesize(&Mat2::new([u.clone(), t.adj().mul(&wl).neg(), t.clone(), u.adj().mul(&wl)], k))
|
||||
}
|
||||
|
||||
/// Each `u` keeps its distance whatever the unit, but the search is over
|
||||
/// every `u`, and one that lost may now win: fewer `T` gates can end
|
||||
/// farther, and one count can take another word.
|
||||
#[test]
|
||||
fn fewer_t_gates_can_end_farther_and_one_count_can_take_another_word() {
|
||||
// The Ising program's zz middle angle at ε = 10⁻³.
|
||||
let (theta, eps) = (-0.1644907717943757, 1e-3);
|
||||
let (now, before) = (rz(theta, eps).unwrap(), rz_by(theta, eps, the_solver_s_own_unit).unwrap());
|
||||
assert_eq!((before.t_count, now.t_count), (29, 27));
|
||||
assert!(now.distance > before.distance && now.distance <= eps, "{} against {}", now.distance, before.distance);
|
||||
// rz(0.1) at ε = 10⁻⁹: 89 T gates both ways, another word, nearer.
|
||||
let (now, before) = (rz(0.1, 1e-9).unwrap(), rz_by(0.1, 1e-9, the_solver_s_own_unit).unwrap());
|
||||
assert_eq!((before.t_count, now.t_count), (89, 89));
|
||||
assert_ne!(now.gates, before.gates);
|
||||
assert!(now.distance < before.distance, "{} against {}", now.distance, before.distance);
|
||||
}
|
||||
|
||||
/// What the unit search can do to a rotation, over the oracle's rows:
|
||||
/// never more `T` gates, no farther at an equal count, always within `ε`.
|
||||
#[test]
|
||||
#[cfg_attr(debug_assertions, ignore = "about twenty-four hundred syntheses: release only")]
|
||||
fn the_unit_search_takes_no_more_t_gates_and_at_one_count_is_no_farther() {
|
||||
let (mut changed, mut at_one_count, mut farther) = (0, 0, 0);
|
||||
for (name, theta, eps, _) in &oracle() {
|
||||
let (now, before) = (rz(*theta, *eps).unwrap(), rz_by(*theta, *eps, the_solver_s_own_unit).unwrap());
|
||||
assert!(now.t_count <= before.t_count, "{name}, ε {eps}: {} T gates, {} before", now.t_count, before.t_count);
|
||||
assert!(now.distance <= *eps, "{name}, ε {eps}: distance {}", now.distance);
|
||||
if now.t_count == before.t_count {
|
||||
assert!(now.distance <= before.distance, "{name}, ε {eps}: {} against {}", now.distance, before.distance);
|
||||
}
|
||||
if now.gates != before.gates {
|
||||
changed += 1;
|
||||
at_one_count += usize::from(now.t_count == before.t_count);
|
||||
}
|
||||
farther += usize::from(now.distance > before.distance);
|
||||
}
|
||||
eprintln!("the unit search: {changed} words changed, {at_one_count} of them at one T count; {farther} rotations farther");
|
||||
assert_eq!((changed, at_one_count, farther), (350, 57, 87));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -506,7 +506,7 @@ fn ising(gross: &PauliCosts, two: &PauliCosts) -> Result<Section, String> {
|
|||
F::I,
|
||||
Class::TheirModel,
|
||||
format!(
|
||||
"pygridsynth 1.0.1, the version the source cites, run on the program's four angles at ε = 5·10⁻⁹, gives 86, 80, 84 and 82 T gates, which make 15,542,400 exactly; it approximates the rotation itself. Ours, the same Ross–Selinger method, gives {}, {}, {} and {}, and also searches the class one global phase e^(iπ/8) away, which a rotation of the pivot allows.",
|
||||
"pygridsynth 1.0.1, the version the source cites, run on the program's four angles at ε = 5·10⁻⁹, gives 86, 80, 84 and 82 T gates, which make 15,542,400 exactly; it approximates the rotation itself. Ours, the same Ross–Selinger method, gives {}, {}, {} and {}: it also searches the class one global phase e^(iπ/8) away, which a rotation of the pivot allows, and of the solver's solution of the norm equation and its unit multiples it keeps the one whose operator takes the fewest T gates. It takes no more than the reference on any of 1,192 angles and precisions (engine::synth::rz's tests).",
|
||||
counts[0], counts[1], counts[2], counts[3]
|
||||
),
|
||||
);
|
||||
|
|
@ -874,7 +874,7 @@ mod tests {
|
|||
assert!(rows["fig9.chosen.gross"].why.contains("bicycle_cliffords 0.1.0"));
|
||||
assert!(rows["fig10a.gross_e4_5k.nt"].why.contains("bicycle_compiler 0.1.0"));
|
||||
let all: String = sections.iter().flat_map(|s| &s.rows).map(|r| format!("{} {} {}\n", r.quantity, r.source, r.why)).collect();
|
||||
for gone in ["not released", "Fig. 16", "shortfall", "press release", "announced", "every R ≠ I"] {
|
||||
for gone in ["not released", "Fig. 16", "shortfall", "press release", "announced", "every R ≠ I", "norm equation's solutions"] {
|
||||
assert!(!all.contains(gone), "{gone}");
|
||||
}
|
||||
assert!(rows["fig10a.gross_e3_5k.nt"].why.contains("Fig. 17") || rows["fig10a.gross_e3_5k.nt_undivided"].why.contains("Fig. 17"));
|
||||
|
|
@ -907,8 +907,8 @@ mod tests {
|
|||
/// The Ising row's text names these.
|
||||
#[test]
|
||||
fn the_ising_angles_take_these_t_counts() {
|
||||
assert_eq!(ising_t_counts(5e-9).unwrap(), [79, 80, 81, 84]);
|
||||
assert_eq!(ising_t_count(5e-9).unwrap(), 14_665_600);
|
||||
assert_eq!(ising_t_counts(5e-9).unwrap(), [79, 80, 79, 82]);
|
||||
assert_eq!(ising_t_count(5e-9).unwrap(), 14_592_000);
|
||||
// pygridsynth 1.0.1's counts on the same angles give the source's total.
|
||||
assert_eq!(80 * (4 * (360 * 86 + 100 * 80) + 360 * 84 + 100 * 82), 15_542_400);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,8 +104,8 @@ table9.gross.nm arXiv:2506.03094 Table 9, source files 4,215,240 4215240 guard 4
|
|||
table9.two.nm arXiv:2506.03094 Table 9, source files 4,238,828 4238828 guard 4.149e6
|
||||
table9.gross.nu arXiv:2506.03094 Table 9, source files 2,270,400 2270400 guard 2.729e6
|
||||
table9.two.nu arXiv:2506.03094 Table 9, source files 2,294,400 2294400 guard 2.368e6
|
||||
table9.nt arXiv:2506.03094 Table 9, source files 15,542,400 15542400 guard 1.467e7
|
||||
s4.nt arXiv:2506.03094 §4; Fig. 1(b), source files 1.9·10⁷ (Fig. 1(b): 19,036,800) 19036800 guard 1.467e7
|
||||
table9.nt arXiv:2506.03094 Table 9, source files 15,542,400 15542400 guard 1.459e7
|
||||
s4.nt arXiv:2506.03094 §4; Fig. 1(b), source files 1.9·10⁷ (Fig. 1(b): 19,036,800) 19036800 guard 1.459e7
|
||||
table9.two.ni arXiv:2506.03094 Table 9, source files 41,636,714,298 41636714298 guard 4.210e10
|
||||
table10.fail.gross.1e-3 arXiv:2506.03094 Table 10 3.33·10⁴ 33300.0 band 100.0
|
||||
table10.fail.gross.1e-4 arXiv:2506.03094 Table 10 7.97·10⁻¹ 0.797 band 0.001
|
||||
|
|
|
|||
|
1193
wai/wai-quantum/tests/data/rz_gridsynth_oracle.csv
Normal file
1193
wai/wai-quantum/tests/data/rz_gridsynth_oracle.csv
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue