wai-quantum: a gate under many controls stays one operation on the state vector, the bit-exact method states what its lowering did, and the error-correction reader takes C_XYZ, E and PAULI_CHANNEL_1 #675
74 changed files with 16071 additions and 4628 deletions
16
.github/workflows/wai-quantum-lang.yml
vendored
16
.github/workflows/wai-quantum-lang.yml
vendored
|
|
@ -79,9 +79,23 @@ jobs:
|
|||
set -euo pipefail
|
||||
cargo test --locked --release --features full --lib qasm 2>&1 | tee "$RUNNER_TEMP/qasm.log"
|
||||
grep -qE '^test result: ok\. [1-9][0-9]+ passed; 0 failed' "$RUNNER_TEMP/qasm.log" || { echo "::error::the reader's tests did not pass"; exit 1; }
|
||||
for m in qasm::diag qasm::lex qasm::parse qasm::value qasm::lang_tests qasm::tests quantum_toolchain::tests::qasm_round_trip; do
|
||||
for m in qasm::diag qasm::lex qasm::parse qasm::value qasm::lang_tests qasm::whole_tests qasm::tests quantum_toolchain::tests::qasm_round_trip; do
|
||||
grep -q "^test $m.* \.\.\. ok$" "$RUNNER_TEMP/qasm.log" || { echo "::error::no test of $m ran"; exit 1; }
|
||||
done
|
||||
# A gate held whole, each test by name.
|
||||
for t in qasm::whole_tests::a_whole_program_lowers_to_what_the_reader_writes \
|
||||
qasm::whole_tests::every_program_of_the_reader_s_corpus_reads_alike_both_ways \
|
||||
qasm::whole_tests::only_what_would_double_stands_whole \
|
||||
qasm::whole_tests::past_what_standard_gates_hold_a_gate_still_reads_whole \
|
||||
qasm::whole_tests::the_bit_exact_circuit_holds_a_whole_gate_under_its_controls \
|
||||
qasm::whole_tests::the_whole_gate_is_nearer_the_ideal_state_than_its_expansion \
|
||||
qasm::whole_tests::the_state_vector_of_a_whole_gate_is_its_expansion_s_within_rounding \
|
||||
qasm::whole_tests::a_run_without_a_whole_gate_is_the_run_it_was \
|
||||
qasm::whole_tests::a_lowering_that_needs_standard_gates_writes_them \
|
||||
qasm::whole_tests::the_bit_exact_report_states_what_the_lowering_did \
|
||||
qasm::whole_tests::a_loop_of_wide_whole_gates_is_held_to_what_a_program_may_hold; do
|
||||
grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/qasm.log" || { echo "::error::$t did not run and pass"; exit 1; }
|
||||
done
|
||||
- name: The conformance corpus
|
||||
shell: bash
|
||||
working-directory: wai/wai-quantum
|
||||
|
|
|
|||
10
.github/workflows/wai-quantum-qec.yml
vendored
10
.github/workflows/wai-quantum-qec.yml
vendored
|
|
@ -113,6 +113,16 @@ jobs:
|
|||
quantum_frame::tests::pauli_targets_agree_between_sampler_and_model \
|
||||
quantum_frame::tests::tags_reach_the_model_and_its_text \
|
||||
quantum_frame::tests::the_text_round_trips \
|
||||
quantum_frame::tests::a_pauli_channel_s_rates_have_its_law \
|
||||
quantum_frame::tests::a_pauli_channel_samples_its_law \
|
||||
quantum_frame::tests::the_cycling_gates_cycle_the_axes \
|
||||
quantum_frame::tests::a_correlated_error_flips_its_targets_together \
|
||||
quantum_frame::tests::a_pauli_channel_with_independent_rates_above_one_half_is_taken \
|
||||
quantum_frame::tests::the_carrier_s_index_is_refused_in_text \
|
||||
quantum_frame::tests::a_correlated_error_with_no_index_left_for_its_carrier_is_refused \
|
||||
quantum_frame::tests::the_cycling_gates_take_no_arguments \
|
||||
quantum_frame::tests::the_qec_page_and_conformance_set_name_these_forms \
|
||||
quantum_frame::tests::the_reference_colour_code_has_the_reference_s_model \
|
||||
engine::mitigate::tests::pauli_channels_round_trip_through_rates_and_fidelities \
|
||||
engine::mitigate::tests::the_twirled_circuit_is_the_job_where_the_noise_is_pauli \
|
||||
engine::mitigate::tests::the_exact_inverse_undoes_the_noise \
|
||||
|
|
|
|||
46
.github/workflows/wai-quantum-registry.yml
vendored
46
.github/workflows/wai-quantum-registry.yml
vendored
|
|
@ -108,6 +108,46 @@ jobs:
|
|||
engine::refusal::tests::the_documented_table_is_the_table; do
|
||||
grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/engine.log" || { echo "::error::$t did not run"; exit 1; }
|
||||
done
|
||||
# A gate held whole through a job, and the meter that holds a matrix
|
||||
# product job, each test by name.
|
||||
for t in engine::catalog::whole_tests::what_takes_standard_gates_gives_what_it_gave \
|
||||
engine::catalog::whole_tests::a_job_on_the_state_vectors_holds_the_gate_whole \
|
||||
engine::catalog::whole_tests::the_bit_exact_method_keeps_its_bits_unless_asked \
|
||||
engine::catalog::whole_tests::a_job_can_ask_for_standard_gates \
|
||||
engine::catalog::whole_tests::every_other_method_takes_standard_gates \
|
||||
engine::catalog::whole_tests::past_the_standard_form_only_the_state_vectors_take_it \
|
||||
engine::catalog::whole_tests::a_gate_in_a_program_that_measures_runs_whole \
|
||||
engine::catalog::whole_tests::a_verdict_holds_a_whole_run_to_its_intent \
|
||||
engine::catalog::whole_tests::a_registered_method_sees_standard_gates \
|
||||
engine::catalog::whole_tests::a_bit_exact_result_states_its_global_phase \
|
||||
engine::catalog::whole_tests::an_angle_below_the_table_is_stated \
|
||||
engine::catalog::whole_tests::a_named_matrix_product_job_is_held_to_the_bond_it_reaches \
|
||||
engine::catalog::whole_tests::a_gate_too_large_for_standard_gates_is_refused_before_they_are_written \
|
||||
engine::catalog::whole_tests::a_tiny_phase_under_many_controls_is_not_read_as_clifford \
|
||||
engine::catalog::whole_tests::a_verdict_with_no_intent_holds_a_run_past_the_standard_form \
|
||||
engine::catalog::whole_tests::a_named_method_on_standard_gates_refuses_as_the_reader_does \
|
||||
engine::catalog::whole_tests::lint_counts_a_program_s_steps_in_standard_gates \
|
||||
engine::catalog::whole_tests::the_refusal_past_16_controls_names_the_option \
|
||||
engine::catalog::whole_tests::options_controls_is_read_and_not_ignored \
|
||||
engine::catalog::whole_tests::the_stated_phase_is_relative_to_the_state_vector \
|
||||
engine::catalog::whole_tests::the_bit_exact_whole_form_gives_the_state_vector_s_amplitudes \
|
||||
engine::catalog::whole_tests::a_bit_exact_result_s_accuracy_members_hold_what_they_say \
|
||||
engine::catalog::whole_tests::the_meter_stops_a_job_that_passes_its_budget \
|
||||
engine::catalog::whole_tests::a_metered_plan_states_the_work_its_meter_allows \
|
||||
engine::catalog::tests::the_mps_plan_bounds_its_run; do
|
||||
grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/engine.log" || { echo "::error::$t did not run"; exit 1; }
|
||||
done
|
||||
cargo test --locked --release --features full --lib -- quantum_mpsim:: quantum_sv::tests::a_gate_under_controls quantum::tests::the_walk_under_controls 2>&1 | tee "$RUNNER_TEMP/meter.log"
|
||||
for t in quantum_mpsim::tests::the_meter_counts_the_bonds_reached_and_changes_nothing \
|
||||
quantum_mpsim::tests::the_meter_charges_each_move_of_the_center \
|
||||
quantum_mpsim::tests::a_far_gate_stops_at_the_update_that_passes_the_budget \
|
||||
quantum_mpsim::tests::an_update_is_priced_at_the_bond_it_spans \
|
||||
quantum_mpsim::tests::the_bound_holds_the_meter \
|
||||
quantum_sv::tests::a_gate_under_controls_is_its_dense_gate_on_any_thread_count \
|
||||
quantum_sv::tests::a_gate_under_controls_must_fit_the_register \
|
||||
quantum::tests::the_walk_under_controls_gives_the_scan_s_bits; do
|
||||
grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/meter.log" || { echo "::error::$t did not run"; exit 1; }
|
||||
done
|
||||
- name: The example, tested and run
|
||||
shell: bash
|
||||
working-directory: wai/wai-quantum
|
||||
|
|
@ -175,3 +215,9 @@ jobs:
|
|||
set -euo pipefail
|
||||
cargo test --locked --target wasm32-wasip2 --features full --lib engine::registry:: 2>&1 | tee "$RUNNER_TEMP/wasip2.log"
|
||||
grep -qE '^test result: ok\. 12 passed; 0 failed' "$RUNNER_TEMP/wasip2.log" || { echo "::error::the registry's tests did not all pass under wasip2"; exit 1; }
|
||||
# The browser's engine runs a gate under controls by this path too.
|
||||
cargo test --locked --target wasm32-wasip2 --features full --lib quantum_sv::tests::a_gate_under_controls 2>&1 | tee "$RUNNER_TEMP/wasip2-sv.log"
|
||||
for t in quantum_sv::tests::a_gate_under_controls_is_its_dense_gate_on_any_thread_count \
|
||||
quantum_sv::tests::a_gate_under_controls_must_fit_the_register; do
|
||||
grep -q "^test $t \.\.\. ok$" "$RUNNER_TEMP/wasip2-sv.log" || { echo "::error::$t did not run under wasip2"; exit 1; }
|
||||
done
|
||||
|
|
|
|||
12
wai/changes/2026-11-05-many-controlled-gates-stay-whole.md
Normal file
12
wai/changes/2026-11-05-many-controlled-gates-stay-whole.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
### wai-quantum: a gate under many controls stays one operation on the state vector, the bit-exact method says what its lowering did, and the error-correction reader takes `C_XYZ`, `E` and `PAULI_CHANNEL_1`
|
||||
|
||||
**What a result gives.** A job on `statevector` whose program holds a gate under controls that used to be written through a phase polynomial (`x`, `y`, `z` or `h` under three or more controls; `s`, `sdg`, `t`, `tdg`, `p`, `rx`, `ry`, `rz`, `u`, `sx` or `sxdg` under two or more) runs that gate as one operation; its amplitudes and expectation values move by double-precision rounding, within the method's bound, its plan's and estimate's `work` fall with the gates it runs, and `"options": {"controls": "standard"}` gives the old bits. A job on `exact` gives the bits it gave, by default: the bit-exact method's bits are the rounding of each gate it applies, so it runs the standard gates unless the job asks for `"options": {"controls": "whole"}`, which holds such a gate whole under its controls, nearer the ideal state by no more than the extension's published bound and with another `state_hash`. `controls` is read: another value is refused, and so is `"whole"` on a job that runs standard gates (`J003`). A result on `exact` also says, in `accuracy`, the global phase its lowering drops and any angle it ran as no phase or moved (below), so a job whose program holds `rz`, `rx`, `ry`, `rxx`, `ryy`, `rzz` or `u`, or such an angle, gives the same bits and hash in a result whose JSON, and so whose receipt, holds those members too. Every other method and request gives what it gave.
|
||||
|
||||
- **The reader.** `qasm::read_whole` keeps a gate whose standard gates would double as one operation (`Op::is_whole`), under as many as 255 controls. `Program::lowered` writes it in standard gates: exactly what `qasm::read` gives of the same text, its steps, timing and records moved with it, one lowering behind both. `read` and `parse` give standard gates as they did, and refuse past 16 controls and past `MAX_OPS` as they did. The Clifford check of a program holding a gate whole counts its standard gates first and refuses what `read` refuses, with `Q208` or `Q210`, before writing any; it reads those gates' phases exactly, so a phase under many controls too small for the reader's tolerance (`ctrl(16) @ p(1e-7)`) is not taken for none. A method's plan makes only the circuit it reads: the bit-exact circuit for `exact`, the Clifford gates for `stabilizer`.
|
||||
- **The state vector.** `StateVector::run_ops` applies such a gate as one (`Controlled`), visiting only the `2^(n − c)` amplitudes its controls leave on; a run without one is `run`, to the bit. Two Grover iterations on 14 qubits run as 130 operations, not 131,186 gates. Asked for, the bit-exact simulator holds the gate under its controls and walks only those indices too; a gate under two or more controls in standard gates (a Toffoli) takes the same walk, to the bit of the scan it replaces.
|
||||
- **What a job counts.** `gates` counts standard gates however the method ran them, as it always has, so a receipt's provenance carries the count it carried, and `lint_qasm`'s `dynamic_steps` counts steps the same way; `operations`, a new optional member of a result and of `lint_qasm`, says what a method holding gates whole ran. A provenance's `plan.work` follows what the method ran. A job with `compile`, `noise`, `mitigate`, `target`, `provider` or a named `fabric` place runs standard gates as it did, as do the other methods, a registered method among them. Methods that need standard gates, named, refuse what those cannot hold as the reader does, on its line, with `Q208` or `Q210`, saying how a job holds such a gate; `auto` passes over them. A job with an `intent` is held to standard gates, since the intent is checked in them; a verdict with none, which both MCP servers ask for on every run, reads the program as it ran. Gates held whole may take 2,000,000 qubits in all (`Q210`), which holds no program the standard form holds.
|
||||
- **What depended on the expansion** did not change: the reader's standard gates, the bit-exact method's hash, a compile, the QIR and jeff exports and the resource estimate's counts, each checked against this engine's output before the change, and the expected hashes of `quantum-conformance/`.
|
||||
- **The bit-exact method says what its lowering did** (`accuracy`, each only when there is something to say). `global_phase`: `rz`, `rx`, `ry`, `rxx`, `ryy`, `rzz` and `u` become phase gates, so method statevector's state of the program that ran is `e^(i·radians)` times the amplitudes given; the phase is exact, in 2^33rds of a turn, whole turns counted. The language's own state can differ from both: its `U` is `e^(iθ/2)` times the matrix both take as `u`, the reader drops an uncontrolled `gphase`, and a compiled job's program is the compile's. `below_table`: a phase gate within the reader's tolerance, 1e-14 of a turn, of a whole turn ran as no phase, as the table's arithmetic has it, and now says so here and in the plan's `why`. `moved_angles`: a phase gate within that tolerance of a dyadic angle, by more than a double's rounding of it. Their `angles` count the lowering's phase gates, so one angle written may be several.
|
||||
- **The matrix-product job.** A job that names `mps` is no longer refused on an estimate at the bond it asks for: it runs under a meter of the bonds it holds (`Chain::run_within`, `Chain::work`), which changes no number, and stops with `J011`, saying where, if those pass one job's work. The meter counts each two-site update, each move of the orthogonality center and each one-qubit gate, and holds the budget at each update, inside a gate that crosses the chain too. The 30-qubit QFT of a basis state at `max_bond` 256 runs, a product state at every step, at bond 1. Its plan says `metered`, and its `work` is then what the meter allows, so a dry run's time is the meter's and such a job does not set the method's measured rate. A job with `auto` is held to the estimate, and told what naming the method does. The estimate now walks the same steps at the bond asked for (`WorkBound`), so it bounds the meter: a plan's `work` for `mps` now counts those moves and its one-qubit gates, so it is larger than before, and `auto` passes over `mps` where they take it past one job's work.
|
||||
- **The error-correction text.** `C_XYZ` and `C_ZYX`; `E` (or `CORRELATED_ERROR`), a flip on a qubit of its own handed to its Pauli targets, so a circuit with one holds one qubit more than its text names; and `PAULI_CHANNEL_1`, as the independent flips with its law, a rate above 1/2 among them where the law needs one (`(0.6, 0.3, 0.05)`). A channel with no such flips (`PAULI_CHANNEL_1(0.1, 0.1, 0)`) is refused with its line. `C_XYZ` and `C_ZYX` take no arguments and refuse one. A text may not name qubit 4294967295, the index the carrier is read under, nor leave a correlated error no index for its carrier (qubit 4294967294 named); both are refused with their line.
|
||||
- **The corpus** ([quantum-cross-simulator/](quantum-cross-simulator/README.md)): rerun, with the bit-exact method's amplitudes held to the reference's with the phase its result states, and before-and-after timings on one host.
|
||||
|
|
@ -35,6 +35,12 @@ The files:
|
|||
coordinates shifted by `SHIFT_COORDS`. A tag on a noise instruction is the
|
||||
tag of every mechanism it makes, and mechanisms with one symptom set but
|
||||
different tags stay apart.
|
||||
- `correlated`: written by hand. `C_XYZ` and `C_ZYX` on a qubit that a
|
||||
detector reads through them; `E` and `CORRELATED_ERROR` on Pauli targets, so
|
||||
the circuit holds one qubit more than its text names; `PAULI_CHANNEL_1`, as
|
||||
the independent flips with its law, one rate above 1/2.
|
||||
- `refuse/no_independent_flips.circuit`: `PAULI_CHANNEL_1(0.1, 0.1, 0)`, which
|
||||
no independent flips give.
|
||||
- `models/decomposed.dem`: written by hand. Decomposed errors, a tagged one,
|
||||
a `repeat` block with `shift_detectors`, detector coordinates and a
|
||||
declared observable no error flips.
|
||||
|
|
|
|||
10
wai/quantum-conformance/qec-text/circuits/correlated.circuit
Normal file
10
wai/quantum-conformance/qec-text/circuits/correlated.circuit
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
R 0 1 2
|
||||
C_XYZ 0
|
||||
C_ZYX 0
|
||||
E(0.01) X0 Y1
|
||||
CORRELATED_ERROR(0.02) Z1 X2
|
||||
PAULI_CHANNEL_1(0.6, 0.3, 0.05) 2
|
||||
M 0 1 2
|
||||
DETECTOR rec[-3]
|
||||
DETECTOR rec[-2]
|
||||
OBSERVABLE_INCLUDE(0) rec[-1]
|
||||
2
wai/quantum-conformance/qec-text/models/correlated.dem
Normal file
2
wai/quantum-conformance/qec-text/models/correlated.dem
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
error(0.8839999999999999) L0
|
||||
error(0.01) D0 D1
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
R 0
|
||||
PAULI_CHANNEL_1(0.1, 0.1, 0) 0
|
||||
M 0
|
||||
DETECTOR rec[-1]
|
||||
|
|
@ -69,6 +69,18 @@
|
|||
"model": "models/tagged.dem",
|
||||
"model_blake3": "eb62f2e8d90f4f9a121314949852929d390112ef82cef91979ae7bd73182a95f",
|
||||
"mechanisms": 10
|
||||
},
|
||||
{
|
||||
"file": "circuits/correlated.circuit",
|
||||
"qubits": 4,
|
||||
"measurements": 3,
|
||||
"detectors": 2,
|
||||
"observables": 1,
|
||||
"tags": [],
|
||||
"text_blake3": "04ecd1c77c5e22ff4570985e360731fc2a3d4eb2a3dad4c6cdda1c44ecb014c9",
|
||||
"model": "models/correlated.dem",
|
||||
"model_blake3": "5610664e2dde57ba866720afe009cc26fb11af8e511a2f31e477577a2a5e623f",
|
||||
"mechanisms": 2
|
||||
}
|
||||
],
|
||||
"models": [
|
||||
|
|
@ -117,6 +129,15 @@
|
|||
"tagged": 6,
|
||||
"text_blake3": "eb62f2e8d90f4f9a121314949852929d390112ef82cef91979ae7bd73182a95f"
|
||||
},
|
||||
{
|
||||
"file": "models/correlated.dem",
|
||||
"detectors": 2,
|
||||
"observables": 1,
|
||||
"mechanisms": 2,
|
||||
"decomposed": 0,
|
||||
"tagged": 0,
|
||||
"text_blake3": "5610664e2dde57ba866720afe009cc26fb11af8e511a2f31e477577a2a5e623f"
|
||||
},
|
||||
{
|
||||
"file": "models/decomposed.dem",
|
||||
"detectors": 7,
|
||||
|
|
@ -128,6 +149,11 @@
|
|||
}
|
||||
],
|
||||
"refuse": [
|
||||
{
|
||||
"file": "refuse/no_independent_flips.circuit",
|
||||
"line": 2,
|
||||
"contains": "no form as independent"
|
||||
},
|
||||
{
|
||||
"file": "refuse/unsupported.circuit",
|
||||
"line": 3,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ held to a reference within a tolerance derived from the arithmetic that produced
|
|||
command regenerates every result here on a clean machine.
|
||||
|
||||
```sh
|
||||
bash wai/quantum-cross-simulator/reproduce.sh --tier full # the whole corpus: 35 to 45 minutes on the hosts below
|
||||
bash wai/quantum-cross-simulator/reproduce.sh --tier full # the whole corpus: 35 to 50 minutes on the hosts below
|
||||
bash wai/quantum-cross-simulator/reproduce.sh # the CI tier, a few minutes
|
||||
bash wai/quantum-cross-simulator/reproduce.sh --self-test # every injected fault must be caught
|
||||
bash wai/quantum-cross-simulator/reproduce.sh --perf --out perf.json
|
||||
|
|
@ -59,8 +59,8 @@ turn, a rational fraction of a turn, or a decimal number of radians.
|
|||
| measurement in the middle | teleportation, reset of an entangled qubit, measuring twice, feedback, a GHZ state by fusion, repeat-until-success: exact laws from every branch |
|
||||
| stabilizer circuits | GHZ and cluster states to 5000 qubits; random Clifford layers at 63, 64, 65, 127, 128 and 129 qubits (either side of a machine word) and to 2000; a Clifford circuit as a state vector and a tableau; outcomes at 40 qubits |
|
||||
| noise | depolarizing channels after X and cx chains, whose expectation values are closed forms |
|
||||
| wide circuits | GHZ to 100 qubits and the QFT at 30 and 40 by matrix product states and contraction; a shallow brickwork at 26; a 40-qubit mirror |
|
||||
| error correction | the rotated surface-code memory experiment in Z (d = 3 … 15) and X, the unrotated layout, repetition codes to d = 25, a noiseless run, a colour code, and hand-written circuits for inverted targets, tags, flip noise, merging, Pauli observables, and three instructions that must be refused |
|
||||
| wide circuits | GHZ to 100 qubits and the QFT at 30 and 40 by matrix product states and contraction, and at 30 at a bond far above what it reaches; a shallow brickwork at 26; a 40-qubit mirror |
|
||||
| error correction | the rotated surface-code memory experiment in Z (d = 3 … 15) and X, the unrotated layout, repetition codes to d = 25, a noiseless run, a colour code whose cycle turns the axes with `C_XYZ`, and hand-written circuits for inverted targets, tags, flip noise, merging, Pauli observables, a correlated error, a general Pauli channel, and two circuits that must be refused |
|
||||
|
||||
## How a result is judged
|
||||
|
||||
|
|
@ -198,13 +198,16 @@ extension states modulus 1.0000189 and within 10^-5 rad. These are integers, the
|
|||
which lowers `rz`, `rx`, `ry` and `U` through phase gates and so differs from the standard's
|
||||
matrices by a global phase (a `strict` distance of up to 1.97 on a rotation by a whole turn).
|
||||
No measurement sees it, and under a control the method is right (`controlled_rz_full_turn`,
|
||||
`gate_crz`, `gate_cu`). The amplitudes a job answers with this method carry that phase, and the
|
||||
result does not say so.
|
||||
`gate_crz`, `gate_cu`). A result on this method states the phase (`accuracy.global_phase`,
|
||||
exactly, in 2^33rds of a turn), and the corpus holds the method's state to the reference's with that
|
||||
phase, nothing fitted.
|
||||
- **Angles below the table.** The bit-exact method reads an angle within 10^-14 of a turn of a
|
||||
dyadic one as that dyadic. A phase of 2π/2^k is therefore refused for k = 33 … 46 (`[Q500]`),
|
||||
and for k ≥ 47 runs as no phase at all, labelled bit-exact: its op-log is the circuit's with the
|
||||
angle moved by 2^-k of a turn, which the op-log check allows by the reader's rule, and its state
|
||||
is within 2.3·10^-14 of the ideal one, far inside the method's resolution. Method `auto` sends
|
||||
is within 2.3·10^-14 of the ideal one, far inside the method's resolution. The result and the
|
||||
plan say so (`accuracy.below_table`, with how many and the largest), and an angle moved to a
|
||||
dyadic neighbour other than zero is stated too (`accuracy.moved_angles`). Method `auto` sends
|
||||
k = 33 … 46 to double precision.
|
||||
- **Held or dropped.** Every double- and single-precision engine here held the phase at every k
|
||||
where its answer can say. The decision-diagram engine dropped it at k = 47, 48, 52 and 60, each
|
||||
|
|
@ -236,37 +239,44 @@ figure. Each row is one engine on one case, as data:
|
|||
packages), as the system counts it for that process alone; the baseline beside it is the same
|
||||
measure given a two-qubit job.
|
||||
|
||||
The last table runs the same harness on the engine under test built before and after a gate
|
||||
under many controls stood whole on its state vector, one build straight after the other on one
|
||||
host; `ops` gives the operations each build ran. `wai.exact_whole` is the bit-exact method asked
|
||||
to hold such a gate whole (`"options": {"controls": "whole"}`), which gives other bits than its
|
||||
default; `wai.exact`, its default, runs standard gates and gives the bits it gave before. Past 16
|
||||
controls the standard form refuses (`[Q208]`), so `grover_n20` has no "before".
|
||||
|
||||
<!-- results: written by tools/report.py from results/*.json; do not edit by hand -->
|
||||
|
||||
### `conformance-linux-x86_64.json`
|
||||
|
||||
Host: AMD RYZEN AI MAX+ 395 w/ Radeon 8060S, Ubuntu 26.04.1 LTS, 32 logical CPUs, 123 GiB, x86_64. Tier full, 2705.1 s. Checks: info 12, known 11, not run 12, pass 1637, refused 12.
|
||||
Host: AMD RYZEN AI MAX+ 395 w/ Radeon 8060S, Ubuntu 26.04.1 LTS, 32 logical CPUs, 123 GiB, x86_64. Tier full, 2911.3 s. Checks: info 12, known 11, not run 14, pass 1738, refused 10.
|
||||
|
||||
| family | `wai.exact` | `wai.statevector` | `wai.stabilizer` | `wai.auto` | `wai.mps` | `wai.tensor_network` | `wai.density_matrix` | `wai.qec` | `aer.statevector` | `aer.qasm_import` | `aer.stabilizer` | `aer.mps` | `aer.density_matrix` | `stim.tableau` | `quimb.dense` | `quimb.tensor_network` | `quimb.mps` | `ddsim.statevector` | `qsim.statevector` | `pairs and references` | `references` |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| bit_order | 18/18 (0.068) | 8/8 (0.015) | 12/12 | 8/8 (0.0055) | | | | | 8/8 (0.016) | 6/6 (0.016) | 5/5 | | | 7/7 | 6/6 (0.019) | 6/6 (0.031) | | 8/8 (0.00015) | 8/8 (0.013) | 15/15 (0.0084) | 5/5 |
|
||||
| clifford_large | 3/3 (0.012) | 1/1 (0.00077) | 34/34 (0.43) | 1/1 (7.8e-05) | | | | | 1/1 (0.00084) | 1/1 (0.00084) | 16/16 (0.51) | | | 19/19 (0.3) | 1/1 (0.0017) | 1/1 (4.3e-08) | | 1/1 (2.4e-07) | 1/1 (0.00052) | | 15/15 |
|
||||
| dynamic | | 12/12 (0.29) | 8/8 (0.21) | 12/12 (0.29) | | | | | 12/12 (0.36) | | | | | 8/8 (0.29) | | | | 8/8 (0.22) · 11 known | 6 not run | | |
|
||||
| gate_conventions | 141/141 (0.066) | 47/47 (0.017) | | 47/47 (0.065) | | | | | 47/47 (0.018) | 47/47 (0.018) | | | | | 46/46 (0.017) · 1 not run | 46/46 (0.032) · 1 not run | | 47/47 (0.00015) | 46/46 (0.026) · 1 not run | | |
|
||||
| ghz | 15/15 (0.042) | 7/7 (0.21) | 2/2 (0.022) | 7/7 (0.022) | | | | | 7/7 (0.059) | 5/5 (0.011) | | | | 2/2 (0.29) | 5/5 (0.0094) | 5/5 (0.041) | | 7/7 (0.13) | 7/7 (0.14) | | 5/5 |
|
||||
| grover | 12/12 (0.029) | 6/6 (0.17) | | 6/6 (0.14) | | | | | 6/6 (0.25) | 4/4 (0.0043) | | | | | 4/4 (0.0079) | 4/4 (0.00029) | | 6/6 (0.25) | 6/6 (0.27) | | 4/4 |
|
||||
| bit_order | 24/24 (0.068) | 8/8 (0.015) | 12/12 | 8/8 (0.0069) | | | | | 8/8 (0.016) | 6/6 (0.016) | 5/5 | | | 7/7 | 6/6 (0.019) | 6/6 (0.031) | | 8/8 (0.00015) | 8/8 (0.013) | 15/15 (0.0084) | 5/5 |
|
||||
| clifford_large | 4/4 (0.012) | 1/1 (0.00077) | 34/34 (0.43) | 1/1 (7.8e-05) | | | | | 1/1 (0.00084) | 1/1 (0.00084) | 16/16 (0.51) | | | 19/19 (0.3) | 1/1 (0.0017) | 1/1 (4.5e-08) | | 1/1 (2.4e-07) | 1/1 (0.00052) | | 15/15 |
|
||||
| dynamic | | 12/12 (0.29) | 8/8 (0.21) | 12/12 (0.29) | | | | | 12/12 (0.36) | | | | | 8/8 (0.29) | | | | 8/8 (0.36) · 11 known | 6 not run | | |
|
||||
| gate_conventions | 188/188 (0.069) | 47/47 (0.017) | | 47/47 (0.066) | | | | | 47/47 (0.018) | 47/47 (0.018) | | | | | 46/46 (0.017) · 1 not run | 46/46 (0.032) · 1 not run | | 47/47 (0.00015) | 46/46 (0.026) · 1 not run | | |
|
||||
| ghz | 20/20 (0.042) | 7/7 (0.21) | 2/2 (0.022) | 7/7 (0.022) | | | | | 7/7 (0.059) | 5/5 (0.011) | | | | 2/2 (0.29) | 5/5 (0.0094) | 5/5 (0.041) | | 7/7 (0.082) | 7/7 (0.14) | | 5/5 |
|
||||
| grover | 16/16 (0.029) | 6/6 (0.12) | | 6/6 (0.14) | | | | | 6/6 (0.25) | 4/4 (0.0043) | | | | | 4/4 (0.0079) | 4/4 (0.00034) | | 6/6 (0.21) | 6/6 (0.27) | | 4/4 |
|
||||
| mirror | 7/7 (0.007) | 3/3 (0.00036) | | | | | | | 3/3 (0.00068) | | | | | | 3/3 (0.00036) | | | 2/2 (7e-05) · 1 not run | 3/3 (0.00057) | | |
|
||||
| noise | | | | | | | 3/3 (0.00093) | | | | | | 3/3 (0.00093) | | | | | | | | |
|
||||
| other | | | | | | | | | | | | | | | | | | | | | 3/3 |
|
||||
| qec | | | | | | | | 26/26 (0.61) · 4 refused as listed | | | | | | 19/19 (0.53) · 1 not run | | | | | | 38/38 (0.0031) | |
|
||||
| qft | 19/19 (0.042) | 9/9 (0.58) | | 8/8 (0.66) | | | | | 9/9 (0.61) | 6/6 (0.009) | | | | | 7/7 (0.0062) | 5/5 (0.0057) | | 9/9 (0.61) | 9/9 (0.63) | 15/15 (0.0027) | 5/5 |
|
||||
| qpe | 9/9 (0.016) · 2 refused as listed | 5/5 (0.0091) | | 5/5 (0.013) | | | | | 5/5 (0.0058) | 5/5 (0.0075) | | | | | 5/5 (0.0071) | 4/4 (0.0014) | | 5/5 (4.8e-06) | 5/5 (0.0034) | | 5/5 |
|
||||
| random_clifford_t | 9/9 (0.036) | 3/3 (0.0047) | | 3/3 (0.0064) | | | | | 3/3 (0.0042) | 3/3 (0.0042) | | | | | 3/3 (0.003) | 3/3 (0.00019) | | 3/3 (3e-05) | 3/3 (0.001) | | |
|
||||
| random_dyadic | 6/6 (0.0067) | 2/2 (0.0018) | | 2/2 (0.0016) | | | | | 2/2 (0.0027) | 2/2 (0.0036) | | | | | 2/2 (0.0019) | 2/2 (0.00016) | | 2/2 (6.8e-06) | 2/2 (0.004) | | |
|
||||
| random_rotations | 3 refused as listed | 5/5 (0.48) | | 5/5 (0.48) | | | | | 5/5 (0.5) | 3/3 (0.0034) | | | | | 3/3 (0.0018) | 3/3 (0.00014) | | 5/5 (0.52) | 5/5 (0.4) | 10/10 (0.0017) | |
|
||||
| small_angle | 47/47 (0.9) · 3 refused as listed | 32/32 (0.016) | 2/2 | 18/18 (0.098) | | | | | 32/32 (0.018) | 29/29 (0.016) | 1/1 | | | 1/1 | 30/30 (0.019) | 29/29 (0.016) | | 29/29 (0.15) | 33/33 (0.0075) | | 1/1 |
|
||||
| wide_mps | | | | | 5/5 (0.00017) | 5/5 (0.00057) | | | | | | 4/4 (0.00023) · 1 not run | | | | 5/5 (0.043) | 5/5 (7.3e-05) | | | | |
|
||||
| qec | | | | | | | | 29/29 (0.61) · 2 refused as listed | | | | | | 22/22 (0.53) · 2 not run | | | | | | 44/44 (0.016) | |
|
||||
| qft | 25/25 (0.042) | 9/9 (0.58) | | 8/8 (0.66) | | | | | 9/9 (0.61) | 6/6 (0.009) | | | | | 7/7 (0.0062) | 5/5 (0.0057) | | 9/9 (0.63) | 9/9 (0.63) | 15/15 (0.0027) | 5/5 |
|
||||
| qpe | 12/12 (0.017) · 2 refused as listed | 5/5 (0.0091) | | 5/5 (0.013) | | | | | 5/5 (0.0058) | 5/5 (0.0075) | | | | | 5/5 (0.0071) | 4/4 (0.0014) | | 5/5 (4.8e-06) | 5/5 (0.0034) | | 5/5 |
|
||||
| random_clifford_t | 12/12 (0.039) | 3/3 (0.0047) | | 3/3 (0.017) | | | | | 3/3 (0.0042) | 3/3 (0.0042) | | | | | 3/3 (0.003) | 3/3 (0.00024) | | 3/3 (3e-05) | 3/3 (0.001) | | |
|
||||
| random_dyadic | 8/8 (0.0067) | 2/2 (0.0018) | | 2/2 (0.0016) | | | | | 2/2 (0.0027) | 2/2 (0.0036) | | | | | 2/2 (0.0019) | 2/2 (0.00015) | | 2/2 (6.8e-06) | 2/2 (0.004) | | |
|
||||
| random_rotations | 3 refused as listed | 5/5 (0.48) | | 5/5 (0.48) | | | | | 5/5 (0.5) | 3/3 (0.0034) | | | | | 3/3 (0.0018) | 3/3 (0.00014) | | 5/5 (0.4) · 1 not run | 5/5 (0.4) | 6/6 (0.0017) | |
|
||||
| small_angle | 62/62 (0.9) · 3 refused as listed | 32/32 (0.016) | 2/2 | 18/18 (0.098) | | | | | 32/32 (0.018) | 29/29 (0.016) | 1/1 | | | 1/1 | 30/30 (0.019) | 29/29 (0.016) | | 29/29 (0.15) | 33/33 (0.0075) | | 1/1 |
|
||||
| wide_mps | | | | | 6/6 (0.00017) | 5/5 (0.00057) | | | | | | 4/4 (0.00023) · 1 not run | | | | 5/5 (0.043) | 5/5 (7.3e-05) | | | | |
|
||||
| wide_tn | | | | | 1/1 (0.00093) | 1/1 (6.4e-07) | | | | | | 1/1 (0.0021) | | | | 1/1 (0.02) | 1/1 (0.0011) | | | 21/21 (0.02) | |
|
||||
|
||||
Known disagreements of other engines with the exact reference (`corpus/known-disagreements.json`):
|
||||
|
||||
- `repeat_until_success`, `ddsim.statevector`: ValueError: [changePermutation] Target must be an injective mapping using only keys and logical qubits from the source permutation. | known: MQT DDSIM 2.7.0's qasm_simulator raises ValueError [changePermutation] on this program (two qubits; qubit 1 measured three times in the middle, with gates between and an x conditioned on each reading; four classical bits) and gives no counts. It raises the same without the conditioned x, and on qubit 1 measured, h, measured again, then qubit 0 measured into a third bit; with no third bit it gives counts. Each is a case of the reproducer.
|
||||
- `measure_twice`, `ddsim.statevector`: 10: 49902 shots, probability 0; 11: 0 shots, expected 50000.0 ± 1084.8 | known: MQT DDSIM 2.7.0's qasm_simulator, given h q[0]; c[0] = measure q[0]; c[1] = measure q[0]; with nothing between the two measurements, returns 00 and 10: c[0] is never set. With x in place of h it gives 10 on every shot; with a barrier between the measurements, 00 and 11; with c[1] measured first and c[0] second, ValueError [changePermutation]. Each is a case of the reproducer, with the circuit built in Qiskit 2.6.2.
|
||||
- `measure_twice`, `ddsim.statevector`: 10: 50067 shots, probability 0; 11: 0 shots, expected 50000.0 ± 1084.8 | known: MQT DDSIM 2.7.0's qasm_simulator, given h q[0]; c[0] = measure q[0]; c[1] = measure q[0]; with nothing between the two measurements, returns 00 and 10: c[0] is never set. With x in place of h it gives 10 on every shot; with a barrier between the measurements, 00 and 11; with c[1] measured first and c[0] second, ValueError [changePermutation]. Each is a case of the reproducer, with the circuit built in Qiskit 2.6.2.
|
||||
- `measure_twice`, `ddsim.statevector`: known: MQT DDSIM 2.7.0's qasm_simulator, given h q[0]; c[0] = measure q[0]; c[1] = measure q[0]; with nothing between the two measurements, returns 00 and 10: c[0] is never set. With x in place of h it gives 10 on every shot; with a barrier between the measurements, 00 and 11; with c[1] measured first and c[0] second, ValueError [changePermutation]. Each is a case of the reproducer, with the circuit built in Qiskit 2.6.2.
|
||||
- `measure_twice`, `ddsim.statevector`, reproducer (corpus/reproducers/ddsim_repeated_measurement.py): h, then c[0] and c[1] measured from the qubit with nothing between: only 00 and 10 come — saw ['00', '10']
|
||||
- `measure_twice`, `ddsim.statevector`, reproducer (corpus/reproducers/ddsim_repeated_measurement.py): x, then the same two measurements: 10 on every shot — saw ['10']
|
||||
|
|
@ -277,7 +287,7 @@ Known disagreements of other engines with the exact reference (`corpus/known-dis
|
|||
- `repeat_until_success`, `ddsim.statevector`, reproducer (corpus/reproducers/ddsim_feedback_rounds.py): qubit 1 measured, h, measured again, then qubit 0 measured into a third bit: the same error — saw ValueError: [changePermutation] Target must be an injective mapping using only keys and logical qubits from the source permutation.
|
||||
- `repeat_until_success`, `ddsim.statevector`, reproducer (corpus/reproducers/ddsim_feedback_rounds.py): the same without qubit 0's measurement into a third bit: counts — saw counts over 4 outcomes
|
||||
|
||||
<details><summary>Refusals by the engine under test, each listed in <code>corpus/expected-refusals.json</code> with its words (12)</summary>
|
||||
<details><summary>Refusals by the engine under test, each listed in <code>corpus/expected-refusals.json</code> with its words (10)</summary>
|
||||
|
||||
- `qpe_t6_third`, `wai.exact`: the angle 2.0943951023931953 is not a dyadic multiple of 2π (2πm/2^k, k ≤ 32) [Q500]
|
||||
- `qpe_t10_third`, `wai.exact`: the angle 2.0943951023931953 is not a dyadic multiple of 2π (2πm/2^k, k ≤ 32) [Q500]
|
||||
|
|
@ -287,15 +297,14 @@ Known disagreements of other engines with the exact reference (`corpus/known-dis
|
|||
- `below_grid_k33`, `wai.exact`: the angle 0.0000000007314590396335798 is not a dyadic multiple of 2π (2πm/2^k, k ≤ 32) [Q500]
|
||||
- `below_grid_k40`, `wai.exact`: the angle 0.000000000005714523747137342 is not a dyadic multiple of 2π (2πm/2^k, k ≤ 32) [Q500]
|
||||
- `tiny_radians`, `wai.exact`: the angle 0.000000000001 is not a dyadic multiple of 2π (2πm/2^k, k ≤ 32) [Q500]
|
||||
- `color_d3`, `wai.qec`: line 16: unsupported instruction 'C_XYZ'
|
||||
- `qec_refused_correlated`, `wai.qec`: line 2: unsupported instruction 'E'
|
||||
- `qec_refused_pauli_channel`, `wai.qec`: line 2: unsupported instruction 'PAULI_CHANNEL_1'
|
||||
- `qec_refused_channel_with_no_independent_form`, `wai.qec`: line 2: PAULI_CHANNEL_1(0.1, 0.1, 0): its exclusive outcomes have no form as independent X, Y and Z flips, which this simulator and a detector error model both need
|
||||
- `qec_refused_random_detector`, `wai.qec`: a detector or observable is not deterministic (it anticommutes with op 0)
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Checks other engines did not make, and why (12)</summary>
|
||||
<details><summary>Checks other engines did not make, and why (14)</summary>
|
||||
|
||||
- `rotations_wide_n20`, `ddsim.statevector`: did not finish within 900 s
|
||||
- `mirror_n20`, `ddsim.statevector`: did not finish within 900 s
|
||||
- `gate_gphase_controlled`, `quimb.dense`: no gate gphase
|
||||
- `gate_gphase_controlled`, `quimb.tensor_network`: no gate gphase
|
||||
|
|
@ -307,6 +316,7 @@ Known disagreements of other engines with the exact reference (`corpus/known-dis
|
|||
- `ghz_by_fusion`, `qsim.statevector`: measurement in the middle (or one qubit measured twice)
|
||||
- `repeat_until_success`, `qsim.statevector`: measurement in the middle (or one qubit measured twice)
|
||||
- `mps_ghz_n100`, `aer.mps`: CircuitTooWideForTarget: 'Number of qubits (100) in circuit-46 is greater than maximum (63) in the coupling_map'
|
||||
- `qec_refused_channel_with_no_independent_form`, `stim.tableau`: ValueError: Encountered the operation PAULI_CHANNEL_1 during error analysis, but this operation requires the `approximate_disjoint_errors` option to be enabled. If you're calling from python, using stim.Circuit.detector_error_model, you need to add the argument approximate_disjoint_errors=True. If
|
||||
- `qec_refused_random_detector`, `stim.tableau`: ValueError: The circuit contains non-deterministic detectors. To make an SVG picture of the problem, you can use the python API like this: your_circuit.diagram('detslice-with-ops-svg', tick=range(0, 5), filter_coords=['D0', ]) or the command line API like this: stim diagram --in your_circui
|
||||
|
||||
</details>
|
||||
|
|
@ -329,33 +339,33 @@ Versions: cirq-core 1.7.0, mqt.core 3.11.0, mqt.ddsim 2.7.0, numpy 2.5.4, python
|
|||
|
||||
### `conformance-macos-arm64.json`
|
||||
|
||||
Host: Apple M3 Ultra, macOS 27.2, 32 logical CPUs, 256 GiB, arm64. Tier full, 2100.0 s. Checks: info 12, known 11, not run 11, pass 1638, refused 12.
|
||||
Host: Apple M3 Ultra, macOS 27.2, 32 logical CPUs, 256 GiB, arm64. Tier full, 2092.6 s. Checks: info 12, known 11, not run 12, pass 1743, refused 10.
|
||||
|
||||
| family | `wai.exact` | `wai.statevector` | `wai.stabilizer` | `wai.auto` | `wai.mps` | `wai.tensor_network` | `wai.density_matrix` | `wai.qec` | `aer.statevector` | `aer.qasm_import` | `aer.stabilizer` | `aer.mps` | `aer.density_matrix` | `stim.tableau` | `quimb.dense` | `quimb.tensor_network` | `quimb.mps` | `ddsim.statevector` | `qsim.statevector` | `pairs and references` | `references` |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| bit_order | 18/18 (0.068) | 8/8 (0.015) | 12/12 | 8/8 (0.0055) | | | | | 8/8 (0.016) | 6/6 (0.016) | 5/5 | | | 7/7 | 6/6 (0.019) | 6/6 (0.031) | | 8/8 (0.00015) | 8/8 (0.013) | 15/15 (0.0084) | 5/5 |
|
||||
| clifford_large | 3/3 (0.012) | 1/1 (0.00077) | 34/34 (0.43) | 1/1 (7.8e-05) | | | | | 1/1 (0.00084) | 1/1 (0.00084) | 16/16 (0.51) | | | 19/19 (0.34) | 1/1 (0.0017) | 1/1 (5e-08) | | 1/1 (2.4e-07) | 1/1 (0.0024) | | 15/15 |
|
||||
| dynamic | | 12/12 (0.29) | 8/8 (0.21) | 12/12 (0.29) | | | | | 12/12 (0.36) | | | | | 8/8 (0.32) | | | | 8/8 (0.23) · 11 known | 6 not run | | |
|
||||
| gate_conventions | 141/141 (0.066) | 47/47 (0.017) | | 47/47 (0.065) | | | | | 47/47 (0.018) | 47/47 (0.018) | | | | | 46/46 (0.017) · 1 not run | 46/46 (0.032) · 1 not run | | 47/47 (0.00015) | 46/46 (0.026) · 1 not run | | |
|
||||
| ghz | 15/15 (0.042) | 7/7 (0.21) | 2/2 (0.022) | 7/7 (0.022) | | | | | 7/7 (0.059) | 5/5 (0.011) | | | | 2/2 (0.29) | 5/5 (0.0094) | 5/5 (0.041) | | 7/7 (0.11) | 7/7 (0.14) | | 5/5 |
|
||||
| grover | 12/12 (0.029) | 6/6 (0.17) | | 6/6 (0.14) | | | | | 6/6 (0.25) | 4/4 (0.0043) | | | | | 4/4 (0.0079) | 4/4 (0.00032) | | 6/6 (0.25) | 6/6 (0.27) | | 4/4 |
|
||||
| bit_order | 24/24 (0.068) | 8/8 (0.015) | 12/12 | 8/8 (0.0069) | | | | | 8/8 (0.016) | 6/6 (0.016) | 5/5 | | | 7/7 | 6/6 (0.019) | 6/6 (0.031) | | 8/8 (0.00015) | 8/8 (0.013) | 15/15 (0.0084) | 5/5 |
|
||||
| clifford_large | 4/4 (0.012) | 1/1 (0.00077) | 34/34 (0.43) | 1/1 (7.8e-05) | | | | | 1/1 (0.00084) | 1/1 (0.00084) | 16/16 (0.51) | | | 19/19 (0.34) | 1/1 (0.0017) | 1/1 (5e-08) | | 1/1 (2.4e-07) | 1/1 (0.0024) | | 15/15 |
|
||||
| dynamic | | 12/12 (0.29) | 8/8 (0.21) | 12/12 (0.29) | | | | | 12/12 (0.36) | | | | | 8/8 (0.32) | | | | 8/8 (0.25) · 11 known | 6 not run | | |
|
||||
| gate_conventions | 188/188 (0.069) | 47/47 (0.017) | | 47/47 (0.066) | | | | | 47/47 (0.018) | 47/47 (0.018) | | | | | 46/46 (0.017) · 1 not run | 46/46 (0.032) · 1 not run | | 47/47 (0.00015) | 46/46 (0.026) · 1 not run | | |
|
||||
| ghz | 20/20 (0.042) | 7/7 (0.21) | 2/2 (0.022) | 7/7 (0.022) | | | | | 7/7 (0.059) | 5/5 (0.011) | | | | 2/2 (0.29) | 5/5 (0.0094) | 5/5 (0.041) | | 7/7 (0.21) | 7/7 (0.14) | | 5/5 |
|
||||
| grover | 16/16 (0.029) | 6/6 (0.12) | | 6/6 (0.14) | | | | | 6/6 (0.25) | 4/4 (0.0043) | | | | | 4/4 (0.0079) | 4/4 (0.0003) | | 6/6 (0.21) | 6/6 (0.27) | | 4/4 |
|
||||
| mirror | 7/7 (0.007) | 3/3 (0.00036) | | | | | | | 3/3 (0.0011) | | | | | | 3/3 (0.00049) | | | 3/3 (0.00079) | 3/3 (0.00059) | | |
|
||||
| noise | | | | | | | 3/3 (0.00093) | | | | | | 3/3 (0.00093) | | | | | | | | |
|
||||
| other | | | | | | | | | | | | | | | | | | | | | 3/3 |
|
||||
| qec | | | | | | | | 26/26 (0.61) · 4 refused as listed | | | | | | 19/19 (0.52) · 1 not run | | | | | | 38/38 (0.0031) | |
|
||||
| qft | 19/19 (0.042) | 9/9 (0.58) | | 8/8 (0.66) | | | | | 9/9 (0.61) | 6/6 (0.009) | | | | | 7/7 (0.0062) | 5/5 (0.0093) | | 9/9 (0.63) | 9/9 (0.63) | 15/15 (0.0027) | 5/5 |
|
||||
| qpe | 9/9 (0.016) · 2 refused as listed | 5/5 (0.0091) | | 5/5 (0.013) | | | | | 5/5 (0.005) | 5/5 (0.0074) | | | | | 5/5 (0.0068) | 4/4 (0.0011) | | 5/5 (7e-06) | 5/5 (0.0037) | | 5/5 |
|
||||
| random_clifford_t | 9/9 (0.036) | 3/3 (0.0047) | | 3/3 (0.0064) | | | | | 3/3 (0.0042) | 3/3 (0.0042) | | | | | 3/3 (0.003) | 3/3 (0.00022) | | 3/3 (3.7e-05) | 3/3 (0.0012) | | |
|
||||
| random_dyadic | 6/6 (0.0067) | 2/2 (0.0018) | | 2/2 (0.0016) | | | | | 2/2 (0.0031) | 2/2 (0.0038) | | | | | 2/2 (0.0014) | 2/2 (0.00016) | | 2/2 (8e-06) | 2/2 (0.004) | | |
|
||||
| random_rotations | 3 refused as listed | 5/5 (0.48) | | 5/5 (0.48) | | | | | 5/5 (0.5) | 3/3 (0.0039) | | | | | 3/3 (0.0019) | 3/3 (0.00014) | | 5/5 (0.56) | 5/5 (0.4) | 10/10 (0.067) | |
|
||||
| small_angle | 47/47 (0.9) · 3 refused as listed | 32/32 (0.016) | 2/2 | 18/18 (0.098) | | | | | 32/32 (0.018) | 29/29 (0.016) | 1/1 | | | 1/1 | 30/30 (0.019) | 29/29 (0.016) | | 29/29 (0.15) | 33/33 (0.0075) | | 1/1 |
|
||||
| wide_mps | | | | | 5/5 (0.00017) | 5/5 (0.00057) | | | | | | 4/4 (0.00023) · 1 not run | | | | 5/5 (0.043) | 5/5 (7.3e-05) | | | | |
|
||||
| qec | | | | | | | | 29/29 (0.61) · 2 refused as listed | | | | | | 22/22 (0.52) · 2 not run | | | | | | 44/44 (0.016) | |
|
||||
| qft | 25/25 (0.042) | 9/9 (0.58) | | 8/8 (0.66) | | | | | 9/9 (0.61) | 6/6 (0.009) | | | | | 7/7 (0.0062) | 5/5 (0.0093) | | 9/9 (0.67) | 9/9 (0.63) | 15/15 (0.0027) | 5/5 |
|
||||
| qpe | 12/12 (0.017) · 2 refused as listed | 5/5 (0.0091) | | 5/5 (0.013) | | | | | 5/5 (0.005) | 5/5 (0.0074) | | | | | 5/5 (0.0068) | 4/4 (0.0011) | | 5/5 (7e-06) | 5/5 (0.0037) | | 5/5 |
|
||||
| random_clifford_t | 12/12 (0.039) | 3/3 (0.0047) | | 3/3 (0.017) | | | | | 3/3 (0.0042) | 3/3 (0.0042) | | | | | 3/3 (0.003) | 3/3 (0.00021) | | 3/3 (3.7e-05) | 3/3 (0.0012) | | |
|
||||
| random_dyadic | 8/8 (0.0067) | 2/2 (0.0018) | | 2/2 (0.0016) | | | | | 2/2 (0.0031) | 2/2 (0.0038) | | | | | 2/2 (0.0014) | 2/2 (0.00016) | | 2/2 (8e-06) | 2/2 (0.004) | | |
|
||||
| random_rotations | 3 refused as listed | 5/5 (0.48) | | 5/5 (0.48) | | | | | 5/5 (0.5) | 3/3 (0.0039) | | | | | 3/3 (0.0019) | 3/3 (0.00014) | | 5/5 (0.49) | 5/5 (0.4) | 10/10 (0.067) | |
|
||||
| small_angle | 62/62 (0.9) · 3 refused as listed | 32/32 (0.016) | 2/2 | 18/18 (0.098) | | | | | 32/32 (0.018) | 29/29 (0.016) | 1/1 | | | 1/1 | 30/30 (0.019) | 29/29 (0.016) | | 29/29 (0.15) | 33/33 (0.0075) | | 1/1 |
|
||||
| wide_mps | | | | | 6/6 (0.00017) | 5/5 (0.00057) | | | | | | 4/4 (0.00023) · 1 not run | | | | 5/5 (0.043) | 5/5 (7.3e-05) | | | | |
|
||||
| wide_tn | | | | | 1/1 (0.00093) | 1/1 (6.4e-07) | | | | | | 1/1 (0.0033) | | | | 1/1 (0.019) | 1/1 (0.0006) | | | 21/21 (0.015) | |
|
||||
|
||||
Known disagreements of other engines with the exact reference (`corpus/known-disagreements.json`):
|
||||
|
||||
- `repeat_until_success`, `ddsim.statevector`: ValueError: [changePermutation] Target must be an injective mapping using only keys and logical qubits from the source permutation. | known: MQT DDSIM 2.7.0's qasm_simulator raises ValueError [changePermutation] on this program (two qubits; qubit 1 measured three times in the middle, with gates between and an x conditioned on each reading; four classical bits) and gives no counts. It raises the same without the conditioned x, and on qubit 1 measured, h, measured again, then qubit 0 measured into a third bit; with no third bit it gives counts. Each is a case of the reproducer.
|
||||
- `measure_twice`, `ddsim.statevector`: 10: 49971 shots, probability 0; 11: 0 shots, expected 50000.0 ± 1084.8 | known: MQT DDSIM 2.7.0's qasm_simulator, given h q[0]; c[0] = measure q[0]; c[1] = measure q[0]; with nothing between the two measurements, returns 00 and 10: c[0] is never set. With x in place of h it gives 10 on every shot; with a barrier between the measurements, 00 and 11; with c[1] measured first and c[0] second, ValueError [changePermutation]. Each is a case of the reproducer, with the circuit built in Qiskit 2.6.2.
|
||||
- `measure_twice`, `ddsim.statevector`: 10: 49689 shots, probability 0; 11: 0 shots, expected 50000.0 ± 1084.8 | known: MQT DDSIM 2.7.0's qasm_simulator, given h q[0]; c[0] = measure q[0]; c[1] = measure q[0]; with nothing between the two measurements, returns 00 and 10: c[0] is never set. With x in place of h it gives 10 on every shot; with a barrier between the measurements, 00 and 11; with c[1] measured first and c[0] second, ValueError [changePermutation]. Each is a case of the reproducer, with the circuit built in Qiskit 2.6.2.
|
||||
- `measure_twice`, `ddsim.statevector`: known: MQT DDSIM 2.7.0's qasm_simulator, given h q[0]; c[0] = measure q[0]; c[1] = measure q[0]; with nothing between the two measurements, returns 00 and 10: c[0] is never set. With x in place of h it gives 10 on every shot; with a barrier between the measurements, 00 and 11; with c[1] measured first and c[0] second, ValueError [changePermutation]. Each is a case of the reproducer, with the circuit built in Qiskit 2.6.2.
|
||||
- `measure_twice`, `ddsim.statevector`, reproducer (corpus/reproducers/ddsim_repeated_measurement.py): h, then c[0] and c[1] measured from the qubit with nothing between: only 00 and 10 come — saw ['00', '10']
|
||||
- `measure_twice`, `ddsim.statevector`, reproducer (corpus/reproducers/ddsim_repeated_measurement.py): x, then the same two measurements: 10 on every shot — saw ['10']
|
||||
|
|
@ -366,7 +376,7 @@ Known disagreements of other engines with the exact reference (`corpus/known-dis
|
|||
- `repeat_until_success`, `ddsim.statevector`, reproducer (corpus/reproducers/ddsim_feedback_rounds.py): qubit 1 measured, h, measured again, then qubit 0 measured into a third bit: the same error — saw ValueError: [changePermutation] Target must be an injective mapping using only keys and logical qubits from the source permutation.
|
||||
- `repeat_until_success`, `ddsim.statevector`, reproducer (corpus/reproducers/ddsim_feedback_rounds.py): the same without qubit 0's measurement into a third bit: counts — saw counts over 4 outcomes
|
||||
|
||||
<details><summary>Refusals by the engine under test, each listed in <code>corpus/expected-refusals.json</code> with its words (12)</summary>
|
||||
<details><summary>Refusals by the engine under test, each listed in <code>corpus/expected-refusals.json</code> with its words (10)</summary>
|
||||
|
||||
- `qpe_t6_third`, `wai.exact`: the angle 2.0943951023931953 is not a dyadic multiple of 2π (2πm/2^k, k ≤ 32) [Q500]
|
||||
- `qpe_t10_third`, `wai.exact`: the angle 2.0943951023931953 is not a dyadic multiple of 2π (2πm/2^k, k ≤ 32) [Q500]
|
||||
|
|
@ -376,14 +386,12 @@ Known disagreements of other engines with the exact reference (`corpus/known-dis
|
|||
- `below_grid_k33`, `wai.exact`: the angle 0.0000000007314590396335798 is not a dyadic multiple of 2π (2πm/2^k, k ≤ 32) [Q500]
|
||||
- `below_grid_k40`, `wai.exact`: the angle 0.000000000005714523747137342 is not a dyadic multiple of 2π (2πm/2^k, k ≤ 32) [Q500]
|
||||
- `tiny_radians`, `wai.exact`: the angle 0.000000000001 is not a dyadic multiple of 2π (2πm/2^k, k ≤ 32) [Q500]
|
||||
- `color_d3`, `wai.qec`: line 16: unsupported instruction 'C_XYZ'
|
||||
- `qec_refused_correlated`, `wai.qec`: line 2: unsupported instruction 'E'
|
||||
- `qec_refused_pauli_channel`, `wai.qec`: line 2: unsupported instruction 'PAULI_CHANNEL_1'
|
||||
- `qec_refused_channel_with_no_independent_form`, `wai.qec`: line 2: PAULI_CHANNEL_1(0.1, 0.1, 0): its exclusive outcomes have no form as independent X, Y and Z flips, which this simulator and a detector error model both need
|
||||
- `qec_refused_random_detector`, `wai.qec`: a detector or observable is not deterministic (it anticommutes with op 0)
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Checks other engines did not make, and why (11)</summary>
|
||||
<details><summary>Checks other engines did not make, and why (12)</summary>
|
||||
|
||||
- `gate_gphase_controlled`, `quimb.dense`: no gate gphase
|
||||
- `gate_gphase_controlled`, `quimb.tensor_network`: no gate gphase
|
||||
|
|
@ -395,6 +403,7 @@ Known disagreements of other engines with the exact reference (`corpus/known-dis
|
|||
- `ghz_by_fusion`, `qsim.statevector`: measurement in the middle (or one qubit measured twice)
|
||||
- `repeat_until_success`, `qsim.statevector`: measurement in the middle (or one qubit measured twice)
|
||||
- `mps_ghz_n100`, `aer.mps`: CircuitTooWideForTarget: 'Number of qubits (100) in circuit-46 is greater than maximum (63) in the coupling_map'
|
||||
- `qec_refused_channel_with_no_independent_form`, `stim.tableau`: ValueError: Encountered the operation PAULI_CHANNEL_1 during error analysis, but this operation requires the `approximate_disjoint_errors` option to be enabled. If you're calling from python, using stim.Circuit.detector_error_model, you need to add the argument approximate_disjoint_errors=True. If
|
||||
- `qec_refused_random_detector`, `stim.tableau`: ValueError: The circuit contains non-deterministic detectors. To make an SVG picture of the problem, you can use the python API like this: your_circuit.diagram('detslice-with-ops-svg', tick=range(0, 5), filter_coords=['D0', ]) or the command line API like this: stim diagram --in your_circui
|
||||
|
||||
</details>
|
||||
|
|
@ -428,93 +437,117 @@ Host: AMD RYZEN AI MAX+ 395 w/ Radeon 8060S, Ubuntu 26.04.1 LTS, 32 logical CPUs
|
|||
|
||||
| case | engine | threads | ops | seconds, median (min) | peak MiB (baseline) | load average before → after |
|
||||
|---|---|---|---|---|---|---|
|
||||
| qft_n20 | `wai.statevector` | all (32) | 230 | 0.205 (0.127) | 26 (5) | 16.6 → 16.6 |
|
||||
| qft_n20 | `aer.statevector` | 32 | 231 | 0.962 (0.774) | 115 (100) | 16.6 → 21.4 |
|
||||
| qft_n20 | `qsim.statevector` | 32 | | 1.25 (1.21) | 195 (188) | 21.4 → 19.3 |
|
||||
| qft_n20 | `ddsim.statevector` | not set | | 0.336 (0.32) | 131 (98) | 20.6 → 20.6 |
|
||||
| qft_n20 | `wai.exact` | 1 (no threads) | 250 | 0.25 (0.247) | 38 (5) | 20.6 → 19.8 |
|
||||
| qft_n20 | `quimb.dense` | not set | | 3.6 (3) | 291 (195) | 19.8 → 22.6 |
|
||||
| qft_n20 | `wai.statevector` | 1 (pinned) | 230 | 0.232 (0.231) | 22 (5) | 22.6 → 22.6 |
|
||||
| qft_n20 | `aer.statevector` | 1 (pinned) | 231 | 0.25 (0.245) | 116 (100) | 22.6 → 21.0 |
|
||||
| qft_n20 | `qsim.statevector` | 1 (pinned) | | 0.038 (0.0343) | 195 (188) | 21.0 → 20.4 |
|
||||
| qft_n20 | `ddsim.statevector` | 1 (pinned) | | 0.368 (0.333) | 132 (98) | 20.4 → 19.8 |
|
||||
| qft_n20 | `wai.exact` | 1 (pinned) | 250 | 0.287 (0.271) | 38 (5) | 19.8 → 19.8 |
|
||||
| qft_n20 | `quimb.dense` | 1 (pinned) | | 1.28 (1.27) | 276 (195) | 19.8 → 18.9 |
|
||||
| qft_n24 | `wai.statevector` | all (32) | 324 | 0.551 (0.545) | 278 (5) | 18.9 → 18.9 |
|
||||
| qft_n24 | `aer.statevector` | 32 | 325 | 1.74 (1.66) | 355 (100) | 18.9 → 22.8 |
|
||||
| qft_n24 | `qsim.statevector` | 32 | | 2.7 (2.09) | 316 (188) | 22.8 → 31.0 |
|
||||
| qft_n24 | `ddsim.statevector` | not set | | 6.15 (5.46) | 612 (98) | 31.0 → 27.0 |
|
||||
| qft_n24 | `wai.exact` | 1 (no threads) | 348 | 7.04 (6.93) | 518 (5) | 27.0 → 21.2 |
|
||||
| qft_n24 | `wai.statevector` | 1 (pinned) | 324 | 6.25 (5.71) | 262 (5) | 21.2 → 36.6 |
|
||||
| qft_n24 | `aer.statevector` | 1 (pinned) | 325 | 8.17 (6.52) | 356 (100) | 36.6 → 24.3 |
|
||||
| qft_n24 | `qsim.statevector` | 1 (pinned) | | 1.7 (1.69) | 315 (188) | 24.3 → 21.0 |
|
||||
| qft_n24 | `ddsim.statevector` | 1 (pinned) | | 8.03 (7.72) | 612 (98) | 21.0 → 14.3 |
|
||||
| qft_n24 | `wai.exact` | 1 (pinned) | 348 | 7.64 (7.44) | 518 (5) | 14.3 → 14.7 |
|
||||
| qft_n26 | `wai.statevector` | all (32) | 377 | 2.25 (2.15) | 1046 (5) | 14.7 → 19.4 |
|
||||
| qft_n26 | `aer.statevector` | 32 | 378 | 7.91 (6.86) | 1125 (100) | 19.4 → 32.9 |
|
||||
| qft_n26 | `qsim.statevector` | 32 | | 4.95 (4.36) | 699 (188) | 32.9 → 36.7 |
|
||||
| qft_n28 | `wai.statevector` | all (32) | 434 | 13.5 (12.2) | 4118 (5) | 36.7 → 49.4 |
|
||||
| qft_n28 | `aer.statevector` | 32 | 435 | 17.3 (15.4) | 4198 (100) | 49.4 → 43.1 |
|
||||
| qft_n28 | `qsim.statevector` | 32 | | 16.5 (13) | 2234 (188) | 43.1 → 47.7 |
|
||||
| rotations_n20 | `wai.statevector` | all (32) | 590 | 0.161 (0.114) | 27 (5) | 47.7 → 47.7 |
|
||||
| rotations_n20 | `aer.statevector` | 32 | 591 | 1.29 (1.26) | 120 (100) | 47.7 → 47.8 |
|
||||
| rotations_n20 | `qsim.statevector` | 32 | | 1.99 (1.79) | 196 (188) | 47.8 → 47.0 |
|
||||
| rotations_n20 | `quimb.dense` | not set | | 31.9 (11.3) | 291 (195) | 47.0 → 57.5 |
|
||||
| rotations_n20 | `wai.statevector` | 1 (pinned) | 590 | 0.508 (0.482) | 23 (5) | 57.5 → 57.5 |
|
||||
| rotations_n20 | `aer.statevector` | 1 (pinned) | 591 | 0.825 (0.814) | 120 (100) | 57.5 → 51.7 |
|
||||
| rotations_n20 | `qsim.statevector` | 1 (pinned) | | 0.0366 (0.0354) | 196 (188) | 51.7 → 49.1 |
|
||||
| rotations_n20 | `quimb.dense` | 1 (pinned) | | 3.01 (2.25) | 276 (195) | 49.1 → 45.6 |
|
||||
| rotations_n24 | `wai.statevector` | all (32) | 710 | 1.09 (1.07) | 281 (5) | 45.6 → 46.5 |
|
||||
| rotations_n24 | `aer.statevector` | 32 | 711 | 4.35 (3.26) | 356 (100) | 46.5 → 48.1 |
|
||||
| rotations_n24 | `qsim.statevector` | 32 | | 11.6 (6.96) | 316 (188) | 48.1 → 55.9 |
|
||||
| rotations_n24 | `wai.statevector` | 1 (pinned) | 710 | 13.7 (13.1) | 263 (5) | 55.9 → 63.0 |
|
||||
| rotations_n24 | `aer.statevector` | 1 (pinned) | 711 | 18 (14.6) | 356 (100) | 63.0 → 27.5 |
|
||||
| rotations_n24 | `qsim.statevector` | 1 (pinned) | | 1.27 (1.27) | 316 (188) | 27.5 → 29.7 |
|
||||
| rotations_n26 | `wai.statevector` | all (32) | 770 | 7.61 (5.28) | 1050 (5) | 29.7 → 40.7 |
|
||||
| rotations_n26 | `aer.statevector` | 32 | 771 | 20.3 (15.7) | 1125 (100) | 40.7 → 48.8 |
|
||||
| rotations_n26 | `qsim.statevector` | 32 | | 4.09 (3.53) | 700 (188) | 48.8 → 41.6 |
|
||||
| grover_n10 | `wai.statevector` | all (32) | 8290 | 0.131 (0.116) | 12 (5) | 41.6 → 41.6 |
|
||||
| grover_n10 | `wai.exact` | 1 (no threads) | 26686 | 0.0358 (0.0355) | 17 (5) | 41.6 → 41.6 |
|
||||
| grover_n10 | `aer.statevector` | 32 | 123 | 0.000314 (0.0003) | 100 (100) | 41.6 → 41.6 |
|
||||
| grover_n10 | `qsim.statevector` | 32 | | 0.0013 (0.00127) | 188 (188) | 41.6 → 41.9 |
|
||||
| grover_n10 | `ddsim.statevector` | not set | | 0.00626 (0.00582) | 99 (98) | 41.9 → 39.6 |
|
||||
| grover_n10 | `wai.statevector` | 1 (pinned) | 8290 | 0.0101 (0.01) | 12 (5) | 39.6 → 39.6 |
|
||||
| grover_n10 | `wai.exact` | 1 (pinned) | 26686 | 0.0519 (0.0517) | 17 (5) | 39.6 → 39.6 |
|
||||
| grover_n10 | `aer.statevector` | 1 (pinned) | 123 | 0.000442 (0.000439) | 100 (100) | 39.6 → 39.6 |
|
||||
| grover_n10 | `qsim.statevector` | 1 (pinned) | | 0.00181 (0.0013) | 187 (188) | 39.6 → 37.3 |
|
||||
| grover_n10 | `ddsim.statevector` | 1 (pinned) | | 0.00739 (0.00572) | 100 (98) | 37.3 → 35.2 |
|
||||
| grover_n14 | `wai.statevector` | all (32) | 131214 | 3.18 (2.91) | 107 (5) | 35.2 → 30.0 |
|
||||
| grover_n14 | `wai.exact` | 1 (no threads) | 557146 | 14.3 (11.7) | 243 (5) | 30.0 → 26.7 |
|
||||
| grover_n14 | `aer.statevector` | 32 | 167 | 0.00308 (0.00296) | 100 (100) | 26.7 → 26.7 |
|
||||
| grover_n14 | `qsim.statevector` | 32 | | 0.239 (0.105) | 188 (188) | 26.7 → 29.8 |
|
||||
| grover_n14 | `ddsim.statevector` | not set | | 0.0074 (0.00696) | 100 (98) | 29.8 → 29.8 |
|
||||
| grover_n14 | `wai.statevector` | 1 (pinned) | 131214 | 2.88 (2.36) | 107 (5) | 29.8 → 31.8 |
|
||||
| grover_n14 | `wai.exact` | 1 (pinned) | 557146 | 14.6 (12.5) | 243 (5) | 31.8 → 25.2 |
|
||||
| grover_n14 | `aer.statevector` | 1 (pinned) | 167 | 0.00517 (0.00505) | 100 (100) | 25.2 → 24.2 |
|
||||
| grover_n14 | `qsim.statevector` | 1 (pinned) | | 0.00248 (0.00248) | 187 (188) | 24.2 → 22.9 |
|
||||
| grover_n14 | `ddsim.statevector` | 1 (pinned) | | 0.00903 (0.00859) | 100 (98) | 22.9 → 21.7 |
|
||||
| clifford_n1000 | `wai.stabilizer` | 1 (no threads) | 35082 | 0.000906 (0.000892) | 90 (5) | 21.7 → 21.7 |
|
||||
| clifford_n1000 | `stim.tableau` | not set | | 0.00116 (0.00115) | 40 (24) | 21.7 → 21.7 |
|
||||
| clifford_n1000 | `aer.stabilizer` | 32 | 35083 | 0.0219 (0.0193) | 207 (121) | 21.7 → 20.7 |
|
||||
| clifford_n5000 | `wai.stabilizer` | 1 (no threads) | 58265 | 0.00767 (0.00606) | 142 (5) | 20.6 → 19.7 |
|
||||
| clifford_n5000 | `stim.tableau` | not set | | 0.00854 (0.00803) | 64 (24) | 19.7 → 19.7 |
|
||||
| surface_z_d5_sample | `wai.qec` | 1 (no threads) | | 0.0735 (0.0707) | 19 (4) | 19.7 → 19.7 |
|
||||
| surface_z_d5_sample | `stim.tableau` | not set | | 0.315 (0.3) | 160 (38) | 19.7 → 18.8 |
|
||||
| surface_z_d5_sample | `wai.qec` | 1 (pinned) | | 0.072 (0.0708) | 19 (4) | 18.8 → 18.8 |
|
||||
| surface_z_d5_sample | `stim.tableau` | 1 (pinned) | | 0.429 (0.287) | 160 (38) | 18.8 → 18.8 |
|
||||
| surface_z_d5_model | `wai.qec` | 1 (no threads) | | 0.00188 (0.00182) | 7 (5) | 18.8 → 18.8 |
|
||||
| surface_z_d5_model | `stim.tableau` | not set | | 0.0032 (0.00306) | 26 (25) | 18.8 → 18.8 |
|
||||
| surface_z_d11_sample | `wai.qec` | 1 (no threads) | | 0.803 (0.79) | 163 (4) | 18.8 → 18.4 |
|
||||
| surface_z_d11_sample | `stim.tableau` | not set | | 2.88 (2.85) | 853 (38) | 18.4 → 17.2 |
|
||||
| surface_z_d11_sample | `wai.qec` | 1 (pinned) | | 1 (0.811) | 163 (4) | 17.2 → 17.5 |
|
||||
| surface_z_d11_sample | `stim.tableau` | 1 (pinned) | | 3.44 (2.77) | 853 (38) | 17.5 → 15.6 |
|
||||
| surface_z_d11_model | `wai.qec` | 1 (no threads) | | 0.0248 (0.0226) | 26 (5) | 15.6 → 15.6 |
|
||||
| surface_z_d11_model | `stim.tableau` | not set | | 0.0474 (0.0452) | 40 (25) | 15.6 → 15.6 |
|
||||
| qft_n40_amplitudes | `wai.mps` | 1 (no threads) | | 0.0113 (0.0111) | 11 (7) | 15.6 → 15.6 |
|
||||
| qft_n40_amplitudes | `aer.mps` | 32 | 861 | 0.00304 (0.00301) | 105 (99) | 15.6 → 15.0 |
|
||||
| qft_n40_amplitudes | `quimb.mps` | not set | | 1.88 (1.86) | 206 (200) | 15.0 → 14.8 |
|
||||
| qft_n40_amplitudes | `wai.tensor_network` | all (32) | | 0.152 (0.143) | 11 (7) | 14.7 → 14.7 |
|
||||
| qft_n40_amplitudes | `quimb.tensor_network` | not set | | 1.06 (0.942) | 220 (205) | 14.7 → 15.3 |
|
||||
| qft_n20 | `wai.statevector` | all (32) | 230 | 0.0363 (0.0347) | 26 (5) | 13.9 → 13.9 |
|
||||
| qft_n20 | `aer.statevector` | 32 | 231 | 0.819 (0.737) | 116 (100) | 13.9 → 13.7 |
|
||||
| qft_n20 | `qsim.statevector` | 32 | | 4.78 (3.16) | 196 (188) | 14.6 → 28.9 |
|
||||
| qft_n20 | `ddsim.statevector` | not set | | 0.349 (0.329) | 132 (99) | 28.0 → 28.0 |
|
||||
| qft_n20 | `wai.exact` | 1 (no threads) | 250 | 0.27 (0.26) | 38 (5) | 28.0 → 27.2 |
|
||||
| qft_n20 | `quimb.dense` | not set | | 4.92 (3.99) | 291 (195) | 27.2 → 31.9 |
|
||||
| qft_n20 | `wai.statevector` | 1 (pinned) | 230 | 0.372 (0.352) | 23 (5) | 31.9 → 31.2 |
|
||||
| qft_n20 | `aer.statevector` | 1 (pinned) | 231 | 0.445 (0.294) | 116 (100) | 31.2 → 31.4 |
|
||||
| qft_n20 | `qsim.statevector` | 1 (pinned) | | 0.0454 (0.0434) | 195 (188) | 31.4 → 29.3 |
|
||||
| qft_n20 | `ddsim.statevector` | 1 (pinned) | | 0.562 (0.5) | 132 (99) | 29.3 → 27.7 |
|
||||
| qft_n20 | `wai.exact` | 1 (pinned) | 250 | 0.393 (0.349) | 38 (5) | 27.7 → 27.7 |
|
||||
| qft_n20 | `quimb.dense` | 1 (pinned) | | 1.37 (1.26) | 276 (195) | 27.7 → 25.0 |
|
||||
| qft_n24 | `wai.statevector` | all (32) | 324 | 0.45 (0.441) | 279 (5) | 25.0 → 26.2 |
|
||||
| qft_n24 | `aer.statevector` | 32 | 325 | 3.9 (1.6) | 356 (100) | 26.2 → 29.5 |
|
||||
| qft_n24 | `qsim.statevector` | 32 | | 2.56 (1.91) | 315 (188) | 29.5 → 30.9 |
|
||||
| qft_n24 | `ddsim.statevector` | not set | | 6.22 (5.34) | 612 (99) | 30.9 → 25.7 |
|
||||
| qft_n24 | `wai.exact` | 1 (no threads) | 348 | 6.6 (6.48) | 518 (5) | 25.7 → 18.8 |
|
||||
| qft_n24 | `wai.statevector` | 1 (pinned) | 324 | 5.82 (5.35) | 263 (5) | 18.8 → 20.5 |
|
||||
| qft_n24 | `aer.statevector` | 1 (pinned) | 325 | 8.17 (6.6) | 356 (100) | 20.5 → 21.2 |
|
||||
| qft_n24 | `qsim.statevector` | 1 (pinned) | | 1.9 (1.63) | 315 (188) | 21.2 → 19.6 |
|
||||
| qft_n24 | `ddsim.statevector` | 1 (pinned) | | 7.36 (6.23) | 612 (99) | 19.6 → 18.5 |
|
||||
| qft_n24 | `wai.exact` | 1 (pinned) | 348 | 7.8 (7) | 518 (5) | 18.5 → 14.9 |
|
||||
| qft_n26 | `wai.statevector` | all (32) | 377 | 2.14 (2.05) | 1047 (5) | 14.9 → 19.9 |
|
||||
| qft_n26 | `aer.statevector` | 32 | 378 | 3.97 (3.7) | 1124 (100) | 19.9 → 23.6 |
|
||||
| qft_n26 | `qsim.statevector` | 32 | | 5.62 (3.93) | 700 (188) | 23.6 → 31.5 |
|
||||
| qft_n28 | `wai.statevector` | all (32) | 434 | 14.2 (12.4) | 4118 (5) | 31.5 → 44.7 |
|
||||
| qft_n28 | `aer.statevector` | 32 | 435 | 16.8 (15.3) | 4198 (100) | 44.7 → 41.8 |
|
||||
| qft_n28 | `qsim.statevector` | 32 | | 12.7 (11.9) | 2234 (188) | 41.8 → 38.5 |
|
||||
| rotations_n20 | `wai.statevector` | all (32) | 590 | 0.0963 (0.0761) | 27 (5) | 38.5 → 38.5 |
|
||||
| rotations_n20 | `aer.statevector` | 32 | 591 | 1.36 (1.06) | 120 (100) | 38.5 → 40.0 |
|
||||
| rotations_n20 | `qsim.statevector` | 32 | | 1.42 (1.22) | 195 (188) | 40.0 → 38.4 |
|
||||
| rotations_n20 | `quimb.dense` | not set | | 15.7 (9.5) | 290 (195) | 38.4 → 54.5 |
|
||||
| rotations_n20 | `wai.statevector` | 1 (pinned) | 590 | 0.613 (0.481) | 23 (5) | 54.5 → 52.2 |
|
||||
| rotations_n20 | `aer.statevector` | 1 (pinned) | 591 | 0.876 (0.709) | 120 (100) | 52.2 → 50.1 |
|
||||
| rotations_n20 | `qsim.statevector` | 1 (pinned) | | 0.0458 (0.0437) | 196 (188) | 50.1 → 45.3 |
|
||||
| rotations_n20 | `quimb.dense` | 1 (pinned) | | 2.25 (2.2) | 276 (195) | 45.3 → 38.4 |
|
||||
| rotations_n24 | `wai.statevector` | all (32) | 710 | 0.892 (0.869) | 282 (5) | 38.4 → 37.3 |
|
||||
| rotations_n24 | `aer.statevector` | 32 | 711 | 2.65 (2.44) | 356 (100) | 37.3 → 38.9 |
|
||||
| rotations_n24 | `qsim.statevector` | 32 | | 1.71 (1.52) | 315 (188) | 38.9 → 38.1 |
|
||||
| rotations_n24 | `wai.statevector` | 1 (pinned) | 710 | 10.4 (9.72) | 263 (5) | 38.1 → 21.1 |
|
||||
| rotations_n24 | `aer.statevector` | 1 (pinned) | 711 | 15.1 (13.3) | 356 (100) | 21.1 → 13.5 |
|
||||
| rotations_n24 | `qsim.statevector` | 1 (pinned) | | 1.54 (1.52) | 316 (188) | 13.5 → 13.6 |
|
||||
| rotations_n26 | `wai.statevector` | all (32) | 770 | 4.12 (3.94) | 1050 (5) | 13.6 → 23.3 |
|
||||
| rotations_n26 | `aer.statevector` | 32 | 771 | 8.7 (8) | 1123 (100) | 23.3 → 33.4 |
|
||||
| rotations_n26 | `qsim.statevector` | 32 | | 4.15 (4.07) | 700 (188) | 33.4 → 35.5 |
|
||||
| grover_n10 | `wai.statevector` | all (32) | 114 | 0.000998 (0.000871) | 6 (5) | 35.5 → 35.5 |
|
||||
| grover_n10 | `wai.exact` | 1 (no threads) | 26686 | 0.0363 (0.0331) | 17 (5) | 35.5 → 35.5 |
|
||||
| grover_n10 | `wai.exact_whole` | 1 (no threads) | 114 | 0.000195 (0.000162) | 6 (5) | 35.5 → 35.5 |
|
||||
| grover_n10 | `aer.statevector` | 32 | 123 | 0.000298 (0.000293) | 100 (100) | 35.5 → 35.5 |
|
||||
| grover_n10 | `qsim.statevector` | 32 | | 0.0014 (0.00129) | 188 (188) | 35.5 → 33.5 |
|
||||
| grover_n10 | `ddsim.statevector` | not set | | 0.00883 (0.00609) | 100 (99) | 33.5 → 33.5 |
|
||||
| grover_n10 | `wai.statevector` | 1 (pinned) | 114 | 0.000446 (0.000321) | 6 (5) | 33.5 → 33.5 |
|
||||
| grover_n10 | `wai.exact` | 1 (pinned) | 26686 | 0.0507 (0.0317) | 17 (5) | 33.5 → 33.5 |
|
||||
| grover_n10 | `wai.exact_whole` | 1 (pinned) | 114 | 0.000273 (0.000271) | 6 (5) | 33.5 → 31.6 |
|
||||
| grover_n10 | `aer.statevector` | 1 (pinned) | 123 | 0.000298 (0.000289) | 100 (100) | 31.6 → 31.6 |
|
||||
| grover_n10 | `qsim.statevector` | 1 (pinned) | | 0.00137 (0.00128) | 187 (188) | 31.6 → 29.7 |
|
||||
| grover_n10 | `ddsim.statevector` | 1 (pinned) | | 0.00742 (0.0058) | 100 (99) | 29.7 → 27.8 |
|
||||
| grover_n14 | `wai.statevector` | all (32) | 158 | 0.00241 (0.00227) | 6 (5) | 27.8 → 27.8 |
|
||||
| grover_n14 | `wai.exact` | 1 (no threads) | 557146 | 11.4 (10.6) | 242 (5) | 27.8 → 14.9 |
|
||||
| grover_n14 | `wai.exact_whole` | 1 (no threads) | 158 | 0.00354 (0.00352) | 6 (5) | 14.9 → 14.9 |
|
||||
| grover_n14 | `aer.statevector` | 32 | 167 | 0.00298 (0.00286) | 100 (100) | 14.9 → 14.9 |
|
||||
| grover_n14 | `qsim.statevector` | 32 | | 0.0773 (0.0661) | 188 (188) | 14.9 → 14.7 |
|
||||
| grover_n14 | `ddsim.statevector` | not set | | 0.00672 (0.0066) | 99 (99) | 14.7 → 14.7 |
|
||||
| grover_n14 | `wai.statevector` | 1 (pinned) | 158 | 0.00225 (0.00156) | 6 (5) | 14.7 → 14.7 |
|
||||
| grover_n14 | `wai.exact` | 1 (pinned) | 557146 | 17.4 (13.8) | 242 (5) | 14.7 → 12.9 |
|
||||
| grover_n14 | `wai.exact_whole` | 1 (pinned) | 158 | 0.00343 (0.0034) | 6 (5) | 12.9 → 12.9 |
|
||||
| grover_n14 | `aer.statevector` | 1 (pinned) | 167 | 0.00296 (0.00281) | 100 (100) | 12.9 → 12.9 |
|
||||
| grover_n14 | `qsim.statevector` | 1 (pinned) | | 0.0018 (0.0018) | 187 (188) | 12.9 → 12.7 |
|
||||
| grover_n14 | `ddsim.statevector` | 1 (pinned) | | 0.0073 (0.00706) | 100 (99) | 12.7 → 12.7 |
|
||||
| grover_n16 | `wai.statevector` | all (32) | 180 | 0.00857 (0.0083) | 7 (5) | 12.7 → 12.7 |
|
||||
| grover_n16 | `wai.exact_whole` | 1 (no threads) | 180 | 0.0169 (0.0162) | 7 (5) | 12.7 → 12.7 |
|
||||
| grover_n16 | `aer.statevector` | 32 | 189 | 0.462 (0.437) | 101 (100) | 12.7 → 15.2 |
|
||||
| grover_n16 | `qsim.statevector` | 32 | | 0.328 (0.288) | 189 (188) | 15.2 → 15.1 |
|
||||
| grover_n16 | `ddsim.statevector` | not set | | 0.00875 (0.0085) | 101 (99) | 15.1 → 15.1 |
|
||||
| grover_n16 | `wai.statevector` | 1 (pinned) | 180 | 0.00965 (0.0096) | 7 (5) | 15.1 → 15.1 |
|
||||
| grover_n16 | `wai.exact_whole` | 1 (pinned) | 180 | 0.0281 (0.0269) | 8 (5) | 15.1 → 15.1 |
|
||||
| grover_n16 | `aer.statevector` | 1 (pinned) | 189 | 0.0186 (0.0182) | 101 (100) | 15.1 → 14.6 |
|
||||
| grover_n16 | `qsim.statevector` | 1 (pinned) | | 0.00342 (0.00234) | 188 (188) | 14.6 → 14.4 |
|
||||
| grover_n16 | `ddsim.statevector` | 1 (pinned) | | 0.0115 (0.0113) | 102 (99) | 14.4 → 14.2 |
|
||||
| grover_n20 | `wai.statevector` | all (32) | 224 | 0.0268 (0.0259) | 25 (5) | 14.2 → 14.2 |
|
||||
| grover_n20 | `wai.exact_whole` | 1 (no threads) | 224 | 0.34 (0.339) | 38 (5) | 14.2 → 14.2 |
|
||||
| grover_n20 | `aer.statevector` | 32 | 233 | 0.72 (0.61) | 124 (100) | 14.2 → 16.7 |
|
||||
| grover_n20 | `qsim.statevector` | 32 | | 0.429 (0.316) | 195 (188) | 16.7 → 16.6 |
|
||||
| grover_n20 | `ddsim.statevector` | not set | | 0.0332 (0.0325) | 132 (99) | 16.6 → 16.5 |
|
||||
| grover_n20 | `wai.statevector` | 1 (pinned) | 224 | 0.204 (0.204) | 22 (5) | 16.5 → 16.5 |
|
||||
| grover_n20 | `wai.exact_whole` | 1 (pinned) | 224 | 0.586 (0.583) | 38 (5) | 16.5 → 16.5 |
|
||||
| grover_n20 | `aer.statevector` | 1 (pinned) | 233 | 0.35 (0.35) | 124 (100) | 16.5 → 16.2 |
|
||||
| grover_n20 | `qsim.statevector` | 1 (pinned) | | 0.0161 (0.0133) | 195 (188) | 16.2 → 15.5 |
|
||||
| grover_n20 | `ddsim.statevector` | 1 (pinned) | | 0.0423 (0.032) | 132 (99) | 15.5 → 15.5 |
|
||||
| clifford_n1000 | `wai.stabilizer` | 1 (no threads) | 35082 | 0.000968 (0.000961) | 90 (5) | 15.5 → 15.6 |
|
||||
| clifford_n1000 | `stim.tableau` | not set | | 0.002 (0.00126) | 40 (24) | 15.6 → 15.6 |
|
||||
| clifford_n1000 | `aer.stabilizer` | 32 | 35083 | 0.0184 (0.0161) | 207 (121) | 15.6 → 15.7 |
|
||||
| clifford_n5000 | `wai.stabilizer` | 1 (no threads) | 58265 | 0.00666 (0.00655) | 142 (5) | 15.9 → 15.6 |
|
||||
| clifford_n5000 | `stim.tableau` | not set | | 0.00839 (0.00829) | 65 (24) | 15.6 → 15.6 |
|
||||
| surface_z_d5_sample | `wai.qec` | 1 (no threads) | | 0.0762 (0.0737) | 19 (4) | 15.6 → 15.7 |
|
||||
| surface_z_d5_sample | `stim.tableau` | not set | | 0.348 (0.337) | 160 (38) | 15.7 → 15.7 |
|
||||
| surface_z_d5_sample | `wai.qec` | 1 (pinned) | | 0.0949 (0.0812) | 19 (4) | 15.7 → 15.7 |
|
||||
| surface_z_d5_sample | `stim.tableau` | 1 (pinned) | | 0.292 (0.273) | 160 (38) | 15.7 → 15.5 |
|
||||
| surface_z_d5_model | `wai.qec` | 1 (no threads) | | 0.00185 (0.00182) | 7 (5) | 15.5 → 15.5 |
|
||||
| surface_z_d5_model | `stim.tableau` | not set | | 0.00321 (0.00319) | 26 (25) | 15.5 → 15.5 |
|
||||
| surface_z_d11_sample | `wai.qec` | 1 (no threads) | | 0.817 (0.811) | 163 (4) | 15.5 → 15.5 |
|
||||
| surface_z_d11_sample | `stim.tableau` | not set | | 3.19 (3.05) | 853 (38) | 15.5 → 16.8 |
|
||||
| surface_z_d11_sample | `wai.qec` | 1 (pinned) | | 1.06 (1.06) | 163 (4) | 16.8 → 16.8 |
|
||||
| surface_z_d11_sample | `stim.tableau` | 1 (pinned) | | 4.05 (3.33) | 853 (38) | 16.8 → 16.8 |
|
||||
| surface_z_d11_model | `wai.qec` | 1 (no threads) | | 0.0442 (0.0373) | 26 (5) | 16.8 → 16.8 |
|
||||
| surface_z_d11_model | `stim.tableau` | not set | | 0.0542 (0.0509) | 40 (25) | 16.8 → 16.8 |
|
||||
| qft_n40_amplitudes | `wai.mps` | 1 (no threads) | | 0.0114 (0.0107) | 11 (7) | 16.8 → 16.8 |
|
||||
| qft_n40_amplitudes | `aer.mps` | 32 | 861 | 0.00371 (0.00352) | 105 (99) | 16.8 → 16.6 |
|
||||
| qft_n40_amplitudes | `quimb.mps` | not set | | 1.98 (1.91) | 206 (200) | 16.6 → 18.2 |
|
||||
| qft_n40_amplitudes | `wai.tensor_network` | all (32) | | 0.155 (0.15) | 12 (7) | 18.2 → 17.9 |
|
||||
| qft_n40_amplitudes | `quimb.tensor_network` | not set | | 1.08 (0.961) | 220 (205) | 17.9 → 17.8 |
|
||||
|
||||
### `performance-macos-arm64.json`
|
||||
|
||||
|
|
@ -529,61 +562,149 @@ Host: Apple M3 Ultra, macOS 27.2, 32 logical CPUs, 256 GiB, arm64. Each figure i
|
|||
|
||||
| case | engine | threads | ops | seconds, median (min) | peak MiB (baseline) | load average before → after |
|
||||
|---|---|---|---|---|---|---|
|
||||
| qft_n20 | `wai.statevector` | all (32) | 230 | 0.0239 (0.0224) | 28 (4) | 24.1 → 24.1 |
|
||||
| qft_n20 | `aer.statevector` | 32 | 231 | 0.0739 (0.0666) | 100 (83) | 24.1 → 30.4 |
|
||||
| qft_n20 | `qsim.statevector` | 32 | | 0.0818 (0.0541) | 197 (187) | 30.4 → 29.5 |
|
||||
| qft_n20 | `ddsim.statevector` | not set | | 0.143 (0.138) | 119 (86) | 29.5 → 29.5 |
|
||||
| qft_n20 | `wai.exact` | 1 (no threads) | 250 | 0.221 (0.215) | 37 (3) | 30.7 → 30.7 |
|
||||
| qft_n20 | `quimb.dense` | not set | | 1.41 (1.39) | 255 (159) | 30.7 → 31.3 |
|
||||
| qft_n24 | `wai.statevector` | all (32) | 324 | 0.48 (0.466) | 288 (4) | 31.8 → 31.8 |
|
||||
| qft_n24 | `aer.statevector` | 32 | 325 | 0.57 (0.562) | 341 (83) | 31.8 → 35.0 |
|
||||
| qft_n24 | `qsim.statevector` | 32 | | 0.58 (0.572) | 318 (187) | 35.0 → 40.6 |
|
||||
| qft_n24 | `ddsim.statevector` | not set | | 2.13 (2.12) | 599 (86) | 40.6 → 42.4 |
|
||||
| qft_n24 | `wai.exact` | 1 (no threads) | 348 | 5.16 (5.11) | 518 (3) | 42.4 → 48.1 |
|
||||
| qft_n26 | `wai.statevector` | all (32) | 377 | 2.14 (2.03) | 1058 (4) | 48.1 → 52.0 |
|
||||
| qft_n26 | `aer.statevector` | 32 | 378 | 1.83 (1.75) | 1109 (83) | 52.0 → 58.4 |
|
||||
| qft_n26 | `qsim.statevector` | 32 | | 2.24 (2.21) | 702 (187) | 58.4 → 57.9 |
|
||||
| qft_n28 | `wai.statevector` | all (32) | 434 | 9.73 (9.69) | 4135 (4) | 57.9 → 67.9 |
|
||||
| qft_n28 | `aer.statevector` | 32 | 435 | 6.79 (6.75) | 4181 (83) | 67.9 → 65.3 |
|
||||
| qft_n28 | `qsim.statevector` | 32 | | 9.01 (8.94) | 2238 (187) | 65.3 → 64.1 |
|
||||
| rotations_n20 | `wai.statevector` | all (32) | 590 | 0.0639 (0.0625) | 28 (4) | 64.1 → 64.1 |
|
||||
| rotations_n20 | `aer.statevector` | 32 | 591 | 0.138 (0.129) | 102 (83) | 64.1 → 64.1 |
|
||||
| rotations_n20 | `qsim.statevector` | 32 | | 0.0629 (0.0603) | 198 (187) | 64.1 → 58.2 |
|
||||
| rotations_n20 | `quimb.dense` | not set | | 2.71 (2.67) | 235 (159) | 58.2 → 51.5 |
|
||||
| rotations_n24 | `wai.statevector` | all (32) | 710 | 0.898 (0.888) | 299 (4) | 51.5 → 50.7 |
|
||||
| rotations_n24 | `aer.statevector` | 32 | 711 | 1.07 (1.05) | 342 (83) | 50.7 → 54.9 |
|
||||
| rotations_n24 | `qsim.statevector` | 32 | | 0.48 (0.466) | 319 (187) | 54.9 → 57.2 |
|
||||
| rotations_n26 | `wai.statevector` | all (32) | 770 | 2.37 (2.29) | 1073 (4) | 57.2 → 61.4 |
|
||||
| rotations_n26 | `aer.statevector` | 32 | 771 | 3.7 (3.68) | 1110 (83) | 61.4 → 62.6 |
|
||||
| rotations_n26 | `qsim.statevector` | 32 | | 1.73 (1.7) | 702 (187) | 62.6 → 62.2 |
|
||||
| grover_n10 | `wai.statevector` | all (32) | 8290 | 0.0152 (0.015) | 11 (4) | 62.2 → 62.2 |
|
||||
| grover_n10 | `wai.exact` | 1 (no threads) | 26686 | 0.0249 (0.0247) | 17 (3) | 62.2 → 62.2 |
|
||||
| grover_n10 | `aer.statevector` | 32 | 123 | 0.000225 (0.000221) | 83 (83) | 62.2 → 58.0 |
|
||||
| grover_n10 | `qsim.statevector` | 32 | | 0.00304 (0.00294) | 189 (187) | 58.0 → 56.3 |
|
||||
| grover_n10 | `ddsim.statevector` | not set | | 0.00523 (0.005) | 87 (86) | 56.3 → 56.3 |
|
||||
| grover_n14 | `wai.statevector` | all (32) | 131214 | 2.96 (2.95) | 104 (4) | 56.3 → 49.0 |
|
||||
| grover_n14 | `wai.exact` | 1 (no threads) | 557146 | 8.44 (8.43) | 261 (3) | 49.0 → 42.5 |
|
||||
| grover_n14 | `aer.statevector` | 32 | 167 | 0.00317 (0.00313) | 84 (83) | 42.5 → 42.5 |
|
||||
| grover_n14 | `qsim.statevector` | 32 | | 0.0116 (0.0111) | 190 (187) | 42.5 → 41.8 |
|
||||
| grover_n14 | `ddsim.statevector` | not set | | 0.00621 (0.00598) | 88 (86) | 41.8 → 41.8 |
|
||||
| clifford_n1000 | `wai.stabilizer` | 1 (no threads) | 35082 | 0.00104 (0.000969) | 95 (3) | 41.8 → 39.4 |
|
||||
| clifford_n1000 | `stim.tableau` | not set | | 0.00173 (0.00163) | 46 (27) | 39.4 → 39.4 |
|
||||
| clifford_n1000 | `aer.stabilizer` | 32 | 35083 | 0.00758 (0.00722) | 192 (100) | 39.4 → 36.8 |
|
||||
| clifford_n5000 | `wai.stabilizer` | 1 (no threads) | 58265 | 0.00978 (0.00834) | 168 (3) | 36.9 → 34.7 |
|
||||
| clifford_n5000 | `stim.tableau` | not set | | 0.0349 (0.0254) | 73 (27) | 34.7 → 32.7 |
|
||||
| surface_z_d5_sample | `wai.qec` | 1 (no threads) | | 0.0872 (0.0867) | 18 (3) | 32.7 → 32.7 |
|
||||
| surface_z_d5_sample | `stim.tableau` | not set | | 0.214 (0.214) | 123 (38) | 32.7 → 32.7 |
|
||||
| surface_z_d5_model | `wai.qec` | 1 (no threads) | | 0.00214 (0.00211) | 6 (3) | 32.7 → 32.7 |
|
||||
| surface_z_d5_model | `stim.tableau` | not set | | 0.00282 (0.00269) | 29 (27) | 32.7 → 32.7 |
|
||||
| surface_z_d11_sample | `wai.qec` | 1 (no threads) | | 0.94 (0.938) | 163 (3) | 32.7 → 30.8 |
|
||||
| surface_z_d11_sample | `stim.tableau` | not set | | 2.05 (2.04) | 938 (38) | 30.8 → 31.5 |
|
||||
| surface_z_d11_model | `wai.qec` | 1 (no threads) | | 0.0234 (0.0231) | 34 (3) | 31.5 → 31.5 |
|
||||
| surface_z_d11_model | `stim.tableau` | not set | | 0.0404 (0.0398) | 49 (27) | 31.5 → 32.1 |
|
||||
| qft_n40_amplitudes | `wai.mps` | 1 (no threads) | | 0.0164 (0.0158) | 10 (5) | 32.1 → 32.1 |
|
||||
| qft_n40_amplitudes | `aer.mps` | 32 | 861 | 0.00538 (0.00534) | 86 (83) | 32.1 → 32.1 |
|
||||
| qft_n40_amplitudes | `quimb.mps` | not set | | 1.89 (1.87) | 166 (161) | 32.1 → 34.9 |
|
||||
| qft_n40_amplitudes | `wai.tensor_network` | all (32) | | 0.181 (0.181) | 11 (5) | 34.9 → 33.7 |
|
||||
| qft_n40_amplitudes | `quimb.tensor_network` | not set | | 0.858 (0.845) | 180 (166) | 33.7 → 32.6 |
|
||||
| qft_n20 | `wai.statevector` | all (32) | 230 | 0.0326 (0.0307) | 28 (4) | 84.8 → 84.9 |
|
||||
| qft_n20 | `aer.statevector` | 32 | 231 | 0.0841 (0.0824) | 101 (83) | 84.9 → 84.9 |
|
||||
| qft_n20 | `qsim.statevector` | 32 | | 0.0905 (0.0771) | 197 (188) | 84.9 → 90.7 |
|
||||
| qft_n20 | `ddsim.statevector` | not set | | 0.145 (0.142) | 119 (86) | 89.8 → 89.8 |
|
||||
| qft_n20 | `wai.exact` | 1 (no threads) | 250 | 0.242 (0.231) | 37 (3) | 89.8 → 89.8 |
|
||||
| qft_n20 | `quimb.dense` | not set | | 1.6 (1.49) | 256 (158) | 89.8 → 87.8 |
|
||||
| qft_n24 | `wai.statevector` | all (32) | 324 | 0.585 (0.54) | 308 (4) | 87.8 → 88.6 |
|
||||
| qft_n24 | `aer.statevector` | 32 | 325 | 1.02 (0.944) | 341 (83) | 88.6 → 88.7 |
|
||||
| qft_n24 | `qsim.statevector` | 32 | | 0.825 (0.777) | 317 (188) | 88.7 → 89.6 |
|
||||
| qft_n24 | `ddsim.statevector` | not set | | 2.24 (2.2) | 599 (86) | 89.6 → 87.0 |
|
||||
| qft_n24 | `wai.exact` | 1 (no threads) | 348 | 5.76 (5.49) | 518 (3) | 87.0 → 91.1 |
|
||||
| qft_n26 | `wai.statevector` | all (32) | 377 | 2.66 (2.16) | 1070 (4) | 91.1 → 107.2 |
|
||||
| qft_n26 | `aer.statevector` | 32 | 378 | 2.28 (2.08) | 1109 (83) | 107.2 → 108.9 |
|
||||
| qft_n26 | `qsim.statevector` | 32 | | 2.77 (2.72) | 701 (188) | 108.9 → 108.4 |
|
||||
| qft_n28 | `wai.statevector` | all (32) | 434 | 10.2 (10.2) | 4143 (4) | 108.4 → 129.7 |
|
||||
| qft_n28 | `aer.statevector` | 32 | 435 | 7.04 (7.02) | 4181 (83) | 129.7 → 133.5 |
|
||||
| qft_n28 | `qsim.statevector` | 32 | | 9.45 (9.41) | 2238 (188) | 133.5 → 107.2 |
|
||||
| rotations_n20 | `wai.statevector` | all (32) | 590 | 0.0699 (0.0663) | 29 (4) | 107.2 → 107.2 |
|
||||
| rotations_n20 | `aer.statevector` | 32 | 591 | 0.195 (0.166) | 102 (83) | 107.2 → 107.1 |
|
||||
| rotations_n20 | `qsim.statevector` | 32 | | 0.143 (0.0835) | 199 (188) | 107.1 → 100.2 |
|
||||
| rotations_n20 | `quimb.dense` | not set | | 2.84 (2.82) | 236 (158) | 100.2 → 99.3 |
|
||||
| rotations_n24 | `wai.statevector` | all (32) | 710 | 0.925 (0.901) | 303 (4) | 99.3 → 102.6 |
|
||||
| rotations_n24 | `aer.statevector` | 32 | 711 | 1.58 (1.51) | 342 (83) | 102.6 → 102.4 |
|
||||
| rotations_n24 | `qsim.statevector` | 32 | | 0.601 (0.585) | 318 (188) | 102.4 → 100.1 |
|
||||
| rotations_n26 | `wai.statevector` | all (32) | 770 | 2.36 (2.3) | 1074 (4) | 100.1 → 98.1 |
|
||||
| rotations_n26 | `aer.statevector` | 32 | 771 | 3.89 (3.83) | 1110 (83) | 98.1 → 94.5 |
|
||||
| rotations_n26 | `qsim.statevector` | 32 | | 1.99 (1.98) | 702 (188) | 94.5 → 91.2 |
|
||||
| grover_n10 | `wai.statevector` | all (32) | 114 | 0.000183 (0.000175) | 4 (4) | 91.2 → 91.2 |
|
||||
| grover_n10 | `wai.exact` | 1 (no threads) | 26686 | 0.0264 (0.0264) | 17 (3) | 91.2 → 91.2 |
|
||||
| grover_n10 | `wai.exact_whole` | 1 (no threads) | 114 | 0.00015 (0.00015) | 4 (3) | 91.2 → 91.2 |
|
||||
| grover_n10 | `aer.statevector` | 32 | 123 | 0.000252 (0.000226) | 84 (83) | 91.2 → 89.9 |
|
||||
| grover_n10 | `qsim.statevector` | 32 | | 0.00337 (0.00307) | 189 (188) | 89.9 → 88.5 |
|
||||
| grover_n10 | `ddsim.statevector` | not set | | 0.00703 (0.00571) | 87 (86) | 88.5 → 88.5 |
|
||||
| grover_n14 | `wai.statevector` | all (32) | 158 | 0.00223 (0.00221) | 4 (4) | 88.5 → 88.5 |
|
||||
| grover_n14 | `wai.exact` | 1 (no threads) | 557146 | 9.18 (9.1) | 261 (3) | 88.5 → 73.9 |
|
||||
| grover_n14 | `wai.exact_whole` | 1 (no threads) | 158 | 0.00322 (0.0032) | 4 (3) | 73.9 → 73.9 |
|
||||
| grover_n14 | `aer.statevector` | 32 | 167 | 0.00316 (0.00313) | 84 (83) | 73.9 → 75.7 |
|
||||
| grover_n14 | `qsim.statevector` | 32 | | 0.0138 (0.0123) | 189 (188) | 75.7 → 71.6 |
|
||||
| grover_n14 | `ddsim.statevector` | not set | | 0.00643 (0.00622) | 88 (86) | 71.6 → 71.6 |
|
||||
| grover_n16 | `wai.statevector` | all (32) | 180 | 0.011 (0.0106) | 7 (4) | 71.6 → 71.6 |
|
||||
| grover_n16 | `wai.exact_whole` | 1 (no threads) | 180 | 0.0148 (0.0146) | 6 (3) | 71.6 → 71.6 |
|
||||
| grover_n16 | `aer.statevector` | 32 | 189 | 0.0194 (0.016) | 86 (83) | 71.6 → 73.7 |
|
||||
| grover_n16 | `qsim.statevector` | 32 | | 0.0151 (0.0134) | 189 (188) | 73.7 → 73.4 |
|
||||
| grover_n16 | `ddsim.statevector` | not set | | 0.00702 (0.00667) | 89 (86) | 73.4 → 73.4 |
|
||||
| grover_n20 | `wai.statevector` | all (32) | 224 | 0.0301 (0.0286) | 28 (4) | 73.4 → 79.4 |
|
||||
| grover_n20 | `wai.exact_whole` | 1 (no threads) | 224 | 0.322 (0.319) | 36 (3) | 79.4 → 79.4 |
|
||||
| grover_n20 | `aer.statevector` | 32 | 233 | 0.0588 (0.0546) | 108 (83) | 79.4 → 79.4 |
|
||||
| grover_n20 | `qsim.statevector` | 32 | | 0.0254 (0.0247) | 197 (188) | 79.4 → 74.8 |
|
||||
| grover_n20 | `ddsim.statevector` | not set | | 0.0159 (0.0157) | 119 (86) | 74.8 → 74.8 |
|
||||
| clifford_n1000 | `wai.stabilizer` | 1 (no threads) | 35082 | 0.00106 (0.00103) | 95 (3) | 74.8 → 74.8 |
|
||||
| clifford_n1000 | `stim.tableau` | not set | | 0.0017 (0.00163) | 46 (27) | 74.8 → 74.8 |
|
||||
| clifford_n1000 | `aer.stabilizer` | 32 | 35083 | 0.00963 (0.00854) | 193 (99) | 74.8 → 69.7 |
|
||||
| clifford_n5000 | `wai.stabilizer` | 1 (no threads) | 58265 | 0.0127 (0.0123) | 168 (3) | 77.3 → 77.3 |
|
||||
| clifford_n5000 | `stim.tableau` | not set | | 0.0441 (0.0331) | 74 (27) | 77.3 → 77.3 |
|
||||
| surface_z_d5_sample | `wai.qec` | 1 (no threads) | | 0.0897 (0.0872) | 18 (3) | 77.3 → 76.9 |
|
||||
| surface_z_d5_sample | `stim.tableau` | not set | | 0.224 (0.219) | 123 (38) | 76.9 → 76.9 |
|
||||
| surface_z_d5_model | `wai.qec` | 1 (no threads) | | 0.00231 (0.00226) | 6 (3) | 76.9 → 76.9 |
|
||||
| surface_z_d5_model | `stim.tableau` | not set | | 0.00279 (0.00269) | 28 (28) | 76.9 → 76.9 |
|
||||
| surface_z_d11_sample | `wai.qec` | 1 (no threads) | | 0.997 (0.981) | 163 (3) | 76.9 → 77.1 |
|
||||
| surface_z_d11_sample | `stim.tableau` | not set | | 2.18 (2.18) | 938 (38) | 77.1 → 75.9 |
|
||||
| surface_z_d11_model | `wai.qec` | 1 (no threads) | | 0.0264 (0.0243) | 33 (3) | 75.9 → 74.7 |
|
||||
| surface_z_d11_model | `stim.tableau` | not set | | 0.04 (0.0399) | 47 (28) | 74.7 → 74.7 |
|
||||
| qft_n40_amplitudes | `wai.mps` | 1 (no threads) | | 0.0167 (0.0159) | 10 (5) | 74.7 → 74.7 |
|
||||
| qft_n40_amplitudes | `aer.mps` | 32 | 861 | 0.00552 (0.00523) | 86 (83) | 74.7 → 74.7 |
|
||||
| qft_n40_amplitudes | `quimb.mps` | not set | | 1.93 (1.92) | 167 (160) | 74.7 → 64.9 |
|
||||
| qft_n40_amplitudes | `wai.tensor_network` | all (32) | | 0.197 (0.194) | 12 (5) | 65.7 → 65.7 |
|
||||
| qft_n40_amplitudes | `quimb.tensor_network` | not set | | 0.934 (0.916) | 180 (167) | 65.7 → 70.7 |
|
||||
|
||||
### `before-after-macos-arm64.json`
|
||||
|
||||
Host: Apple M3 Ultra, macOS 27.2, 32 logical CPUs, 256 GiB, arm64. The same harness and circuits, with the engine under test built before and after a gate under many controls stood whole on its state vector, one build straight after the other: each figure is the engine's own call, the median of 5 runs (the minimum in brackets), and ops are the operations it ran.
|
||||
|
||||
`wai.exact_whole`'s before rows ran a build with no such option, so they are standard gates, as their ops show. The last column is the one-minute load as each build's row began.
|
||||
|
||||
| case | engine | threads | before: ops | before: seconds, median (min) | after: ops | after: seconds, median (min) | load average, before build → after build |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| qft_n20 | `wai.statevector` | all (32) | 230 | 0.0227 (0.0223) | 230 | 0.0326 (0.0307) | 30.9 → 84.8 |
|
||||
| qft_n20 | `aer.statevector` | 32 | 231 | 0.0708 (0.0672) | 231 | 0.0841 (0.0824) | 30.9 → 84.9 |
|
||||
| qft_n20 | `qsim.statevector` | 32 | | 0.0564 (0.0557) | | 0.0905 (0.0771) | 30.9 → 84.9 |
|
||||
| qft_n20 | `ddsim.statevector` | not set | | 0.14 (0.14) | | 0.145 (0.142) | 29.4 → 89.8 |
|
||||
| qft_n20 | `wai.exact` | 1 (no threads) | 250 | 0.22 (0.214) | 250 | 0.242 (0.231) | 32.9 → 89.8 |
|
||||
| qft_n20 | `quimb.dense` | not set | | 1.41 (1.38) | | 1.6 (1.49) | 32.9 → 89.8 |
|
||||
| qft_n24 | `wai.statevector` | all (32) | 324 | 0.474 (0.468) | 324 | 0.585 (0.54) | 32.5 → 87.8 |
|
||||
| qft_n24 | `aer.statevector` | 32 | 325 | 0.565 (0.532) | 325 | 1.02 (0.944) | 32.5 → 88.6 |
|
||||
| qft_n24 | `qsim.statevector` | 32 | | 0.555 (0.528) | | 0.825 (0.777) | 33.5 → 88.7 |
|
||||
| qft_n24 | `ddsim.statevector` | not set | | 2.12 (2.12) | | 2.24 (2.2) | 39.0 → 89.6 |
|
||||
| qft_n24 | `wai.exact` | 1 (no threads) | 348 | 5.01 (5) | 348 | 5.76 (5.49) | 38.2 → 87.0 |
|
||||
| qft_n26 | `wai.statevector` | all (32) | 377 | 2.1 (2.06) | 377 | 2.66 (2.16) | 42.0 → 91.1 |
|
||||
| qft_n26 | `aer.statevector` | 32 | 378 | 1.84 (1.76) | 378 | 2.28 (2.08) | 48.6 → 107.2 |
|
||||
| qft_n26 | `qsim.statevector` | 32 | | 2.11 (2.09) | | 2.77 (2.72) | 51.3 → 108.9 |
|
||||
| qft_n28 | `wai.statevector` | all (32) | 434 | 9.44 (9.32) | 434 | 10.2 (10.2) | 44.6 → 108.4 |
|
||||
| qft_n28 | `aer.statevector` | 32 | 435 | 6.72 (6.58) | 435 | 7.04 (7.02) | 57.7 → 129.7 |
|
||||
| qft_n28 | `qsim.statevector` | 32 | | 8.98 (8.9) | | 9.45 (9.41) | 63.9 → 133.5 |
|
||||
| rotations_n20 | `wai.statevector` | all (32) | 590 | 0.0619 (0.0609) | 590 | 0.0699 (0.0663) | 55.2 → 107.2 |
|
||||
| rotations_n20 | `aer.statevector` | 32 | 591 | 0.13 (0.123) | 591 | 0.195 (0.166) | 55.2 → 107.2 |
|
||||
| rotations_n20 | `qsim.statevector` | 32 | | 0.0568 (0.0534) | | 0.143 (0.0835) | 55.2 → 107.1 |
|
||||
| rotations_n20 | `quimb.dense` | not set | | 2.61 (2.6) | | 2.84 (2.82) | 53.9 → 100.2 |
|
||||
| rotations_n24 | `wai.statevector` | all (32) | 710 | 0.982 (0.909) | 710 | 0.925 (0.901) | 53.7 → 99.3 |
|
||||
| rotations_n24 | `aer.statevector` | 32 | 711 | 1.3 (1.17) | 711 | 1.58 (1.51) | 59.2 → 102.6 |
|
||||
| rotations_n24 | `qsim.statevector` | 32 | | 0.526 (0.463) | | 0.601 (0.585) | 59.4 → 102.4 |
|
||||
| rotations_n26 | `wai.statevector` | all (32) | 770 | 2.32 (2.3) | 770 | 2.36 (2.3) | 53.0 → 100.1 |
|
||||
| rotations_n26 | `aer.statevector` | 32 | 771 | 3.92 (3.63) | 771 | 3.89 (3.83) | 55.1 → 98.1 |
|
||||
| rotations_n26 | `qsim.statevector` | 32 | | 1.77 (1.66) | | 1.99 (1.98) | 57.8 → 94.5 |
|
||||
| grover_n10 | `wai.statevector` | all (32) | 8290 | 0.0156 (0.0152) | 114 | 0.000183 (0.000175) | 49.9 → 91.2 |
|
||||
| grover_n10 | `wai.exact` | 1 (no threads) | 26686 | 0.0251 (0.0247) | 26686 | 0.0264 (0.0264) | 49.9 → 91.2 |
|
||||
| grover_n10 | `wai.exact_whole` | 1 (no threads) | 26686 | 0.0249 (0.0248) | 114 | 0.00015 (0.00015) | 49.9 → 91.2 |
|
||||
| grover_n10 | `aer.statevector` | 32 | 123 | 0.000224 (0.000219) | 123 | 0.000252 (0.000226) | 47.0 → 91.2 |
|
||||
| grover_n10 | `qsim.statevector` | 32 | | 0.00315 (0.00293) | | 0.00337 (0.00307) | 47.0 → 89.9 |
|
||||
| grover_n10 | `ddsim.statevector` | not set | | 0.0054 (0.00457) | | 0.00703 (0.00571) | 44.2 → 88.5 |
|
||||
| grover_n14 | `wai.statevector` | all (32) | 131214 | 3 (2.97) | 158 | 0.00223 (0.00221) | 44.2 → 88.5 |
|
||||
| grover_n14 | `wai.exact` | 1 (no threads) | 557146 | 8.48 (8.39) | 557146 | 9.18 (9.1) | 46.0 → 88.5 |
|
||||
| grover_n14 | `wai.exact_whole` | 1 (no threads) | 557146 | 8.45 (8.37) | 158 | 0.00322 (0.0032) | 35.3 → 73.9 |
|
||||
| grover_n14 | `aer.statevector` | 32 | 167 | 0.00318 (0.00303) | 167 | 0.00316 (0.00313) | 35.0 → 73.9 |
|
||||
| grover_n14 | `qsim.statevector` | 32 | | 0.0114 (0.0102) | | 0.0138 (0.0123) | 34.7 → 75.7 |
|
||||
| grover_n14 | `ddsim.statevector` | not set | | 0.0059 (0.00558) | | 0.00643 (0.00622) | 34.6 → 71.6 |
|
||||
| grover_n16 | `wai.statevector` | all (32) | 524452 | 16.3 (15.5) | 180 | 0.011 (0.0106) | 34.6 → 71.6 |
|
||||
| grover_n16 | `wai.exact_whole` | 1 (no threads) | 2490472 | 151 (151) | 180 | 0.0148 (0.0146) | 35.1 → 71.6 |
|
||||
| grover_n16 | `aer.statevector` | 32 | 189 | 0.0168 (0.0137) | 189 | 0.0194 (0.016) | 66.7 → 71.6 |
|
||||
| grover_n16 | `qsim.statevector` | 32 | | 0.0174 (0.0158) | | 0.0151 (0.0134) | 66.7 → 73.7 |
|
||||
| grover_n16 | `ddsim.statevector` | not set | | 0.00663 (0.00625) | | 0.00702 (0.00667) | 70.6 → 73.4 |
|
||||
| grover_n20 | `wai.statevector` | all (32) | | not run: line 34: modifiers add at most 16 controls here [Q208] | 224 | 0.0301 (0.0286) | 70.6 → 73.4 |
|
||||
| grover_n20 | `wai.exact_whole` | 1 (no threads) | | not run: line 34: modifiers add at most 16 controls here [Q208] | 224 | 0.322 (0.319) | 70.6 → 79.4 |
|
||||
| grover_n20 | `aer.statevector` | 32 | 233 | 0.063 (0.0566) | 233 | 0.0588 (0.0546) | 70.6 → 79.4 |
|
||||
| grover_n20 | `qsim.statevector` | 32 | | 0.032 (0.0248) | | 0.0254 (0.0247) | 70.6 → 79.4 |
|
||||
| grover_n20 | `ddsim.statevector` | not set | | 0.0179 (0.0166) | | 0.0159 (0.0157) | 74.4 → 74.8 |
|
||||
| clifford_n1000 | `wai.stabilizer` | 1 (no threads) | 35082 | 0.00102 (0.00101) | 35082 | 0.00106 (0.00103) | 78.1 → 74.8 |
|
||||
| clifford_n1000 | `stim.tableau` | not set | | 0.00163 (0.0016) | | 0.0017 (0.00163) | 78.1 → 74.8 |
|
||||
| clifford_n1000 | `aer.stabilizer` | 32 | 35083 | 0.0088 (0.00803) | 35083 | 0.00963 (0.00854) | 78.1 → 74.8 |
|
||||
| clifford_n5000 | `wai.stabilizer` | 1 (no threads) | 58265 | 0.0124 (0.0112) | 58265 | 0.0127 (0.0123) | 85.1 → 77.3 |
|
||||
| clifford_n5000 | `stim.tableau` | not set | | 0.0389 (0.0299) | | 0.0441 (0.0331) | 85.6 → 77.3 |
|
||||
| surface_z_d5_sample | `wai.qec` | 1 (no threads) | | 0.0913 (0.0879) | | 0.0897 (0.0872) | 85.6 → 77.3 |
|
||||
| surface_z_d5_sample | `stim.tableau` | not set | | 0.228 (0.222) | | 0.224 (0.219) | 85.6 → 76.9 |
|
||||
| surface_z_d5_model | `wai.qec` | 1 (no threads) | | 0.00231 (0.00223) | | 0.00231 (0.00226) | 86.8 → 76.9 |
|
||||
| surface_z_d5_model | `stim.tableau` | not set | | 0.00329 (0.00271) | | 0.00279 (0.00269) | 86.8 → 76.9 |
|
||||
| surface_z_d11_sample | `wai.qec` | 1 (no threads) | | 0.968 (0.962) | | 0.997 (0.981) | 86.8 → 76.9 |
|
||||
| surface_z_d11_sample | `stim.tableau` | not set | | 2.21 (2.19) | | 2.18 (2.18) | 88.7 → 77.1 |
|
||||
| surface_z_d11_model | `wai.qec` | 1 (no threads) | | 0.0251 (0.0245) | | 0.0264 (0.0243) | 87.2 → 75.9 |
|
||||
| surface_z_d11_model | `stim.tableau` | not set | | 0.0443 (0.0405) | | 0.04 (0.0399) | 87.2 → 74.7 |
|
||||
| qft_n40_amplitudes | `wai.mps` | 1 (no threads) | | 0.0163 (0.0161) | | 0.0167 (0.0159) | 89.1 → 74.7 |
|
||||
| qft_n40_amplitudes | `aer.mps` | 32 | 861 | 0.00552 (0.00538) | 861 | 0.00552 (0.00523) | 89.1 → 74.7 |
|
||||
| qft_n40_amplitudes | `quimb.mps` | not set | | 1.96 (1.93) | | 1.93 (1.92) | 89.1 → 74.7 |
|
||||
| qft_n40_amplitudes | `wai.tensor_network` | all (32) | | 0.188 (0.187) | | 0.197 (0.194) | 86.7 → 65.7 |
|
||||
| qft_n40_amplitudes | `quimb.tensor_network` | not set | | 0.985 (0.972) | | 0.934 (0.916) | 85.4 → 65.7 |
|
||||
|
||||
<!-- end of results -->
|
||||
|
||||
|
|
@ -609,11 +730,14 @@ Host: Apple M3 Ultra, macOS 27.2, 32 logical CPUs, 256 GiB, arm64. Each figure i
|
|||
GPU state vectors of any engine; decoders; noise beyond depolarizing channels in closed form.
|
||||
- **What the engine under test refuses** is listed in
|
||||
[`corpus/expected-refusals.json`](corpus/expected-refusals.json), with the words it must give.
|
||||
Its frame simulator does not implement `C_XYZ`, correlated errors (`E`) or general Pauli
|
||||
channels, so the reference generator's colour-code memory circuits do not run on it. Its
|
||||
matrix-product-state job refuses the 30- and 40-qubit QFT at `max_bond` 256 as more work than one
|
||||
job may take (`[J011]`): its estimate counts the bond asked for, not the bond the circuit
|
||||
reaches. The corpus runs it at 64, where the bond it reaches is recorded in the results.
|
||||
Its frame simulator refuses a Pauli channel whose exclusive outcomes no independent flips can
|
||||
give (`PAULI_CHANNEL_1(0.1, 0.1, 0)`, whose Z would need a rate below zero), with its line: a
|
||||
detector error model of independent mechanisms cannot state it either. A general Pauli channel
|
||||
it can take (`qec_pauli_channel`) it writes as the independent flips with the channel's law, and
|
||||
its model matches the other engine's mechanism by mechanism. A job
|
||||
naming method `mps` runs under a meter of the bonds it holds (`mps_qft_n30_bond256`) and stops
|
||||
with `[J011]` if those pass one job's work; one naming `auto` keeps to the estimate at the bond
|
||||
asked for.
|
||||
- **What the adapters do not drive.** The qsim adapter runs no measurement in the middle of a
|
||||
circuit, so those entries are `not run` for it; that is this adapter's limit. `aer.mps` is
|
||||
not run at 100 qubits: its target holds 63. An engine that has not answered a circuit within
|
||||
|
|
@ -666,5 +790,6 @@ provider template, and `tools/report.py --check`, which holds this page's result
|
|||
| `tools/compare.py` | the run: references, tolerances, injected faults, the results file |
|
||||
| `tools/perf.py` | run time and peak memory |
|
||||
| `tools/report.py` | this page's results section |
|
||||
| `tools/before_after.py` | one host's timings of two builds side by side, from `tools/perf.py` run on each |
|
||||
| `provider/` | the provider template and its test |
|
||||
| `results/` | the committed runs |
|
||||
|
|
|
|||
|
|
@ -58,25 +58,11 @@
|
|||
"reason": "an angle no grid of 2^32 holds within 10^-14 of a turn"
|
||||
},
|
||||
{
|
||||
"entry": "color_d3",
|
||||
"entry": "qec_refused_channel_with_no_independent_form",
|
||||
"engine": "wai.qec",
|
||||
"must": "may",
|
||||
"text": "unsupported instruction 'C_XYZ'",
|
||||
"reason": "the frame simulator does not implement C_XYZ"
|
||||
},
|
||||
{
|
||||
"entry": "qec_refused_correlated",
|
||||
"engine": "wai.qec",
|
||||
"must": "may",
|
||||
"text": "unsupported instruction 'E'",
|
||||
"reason": "the frame simulator does not implement correlated errors"
|
||||
},
|
||||
{
|
||||
"entry": "qec_refused_pauli_channel",
|
||||
"engine": "wai.qec",
|
||||
"must": "may",
|
||||
"text": "unsupported instruction 'PAULI_CHANNEL_1'",
|
||||
"reason": "the frame simulator does not implement general Pauli channels"
|
||||
"must": "refuse",
|
||||
"text": "no form as independent",
|
||||
"reason": "a Pauli channel no independent flips can give is refused, never approximated"
|
||||
},
|
||||
{
|
||||
"entry": "qec_refused_random_detector",
|
||||
|
|
|
|||
|
|
@ -1901,6 +1901,21 @@
|
|||
"gates": 860,
|
||||
"sha256": "acd8a1a61d5131a006b2fba9a3fd5eef79fcb6d407323fb32080d8c117b09b86"
|
||||
},
|
||||
{
|
||||
"id": "mps_qft_n30_bond256",
|
||||
"family": "wide_mps",
|
||||
"params": {
|
||||
"n": 30,
|
||||
"x": 715827883,
|
||||
"max_bond": 256
|
||||
},
|
||||
"reason": "The 30-qubit QFT at a bond far above what it reaches: a job naming the method runs under a meter of the bond it holds, and stays exact.",
|
||||
"expect": "amplitudes",
|
||||
"tier": "full",
|
||||
"qubits": 30,
|
||||
"gates": 496,
|
||||
"sha256": "a85588ba6e9133b1d19ff9cae05c2f1b599755e1738458a888eff79ec67079e1"
|
||||
},
|
||||
{
|
||||
"id": "tn_shallow_n26",
|
||||
"family": "wide_tn",
|
||||
|
|
@ -2150,7 +2165,7 @@
|
|||
"rounds": 3,
|
||||
"p": 0.002
|
||||
},
|
||||
"reason": "A colour code with C_XYZ gates: an instruction the frame simulator does not implement must be refused with its line, never skipped.",
|
||||
"reason": "A colour code, whose syndrome cycle turns the axes with C_XYZ gates: the model must match mechanism by mechanism, and every detector's rate must follow it.",
|
||||
"expect": "qec",
|
||||
"tier": "full",
|
||||
"sha256": "bcda6739a090378385999ac51a2a66e2cfcee038796f1e12e9b8efcd9c33f75c"
|
||||
|
|
|
|||
499
wai/quantum-cross-simulator/corpus/qasm/mps_qft_n30_bond256.qasm
Normal file
499
wai/quantum-cross-simulator/corpus/qasm/mps_qft_n30_bond256.qasm
Normal file
|
|
@ -0,0 +1,499 @@
|
|||
OPENQASM 3.0;
|
||||
include "stdgates.inc";
|
||||
qubit[30] q;
|
||||
x q[0];
|
||||
x q[1];
|
||||
x q[3];
|
||||
x q[5];
|
||||
x q[7];
|
||||
x q[9];
|
||||
x q[11];
|
||||
x q[13];
|
||||
x q[15];
|
||||
x q[17];
|
||||
x q[19];
|
||||
x q[21];
|
||||
x q[23];
|
||||
x q[25];
|
||||
x q[27];
|
||||
x q[29];
|
||||
h q[29];
|
||||
cp(pi/2) q[28], q[29];
|
||||
cp(pi/4) q[27], q[29];
|
||||
cp(pi/8) q[26], q[29];
|
||||
cp(pi/16) q[25], q[29];
|
||||
cp(pi/32) q[24], q[29];
|
||||
cp(pi/64) q[23], q[29];
|
||||
cp(pi/128) q[22], q[29];
|
||||
cp(pi/256) q[21], q[29];
|
||||
cp(pi/512) q[20], q[29];
|
||||
cp(pi/1024) q[19], q[29];
|
||||
cp(pi/2048) q[18], q[29];
|
||||
cp(pi/4096) q[17], q[29];
|
||||
cp(pi/8192) q[16], q[29];
|
||||
cp(pi/16384) q[15], q[29];
|
||||
cp(pi/32768) q[14], q[29];
|
||||
cp(pi/65536) q[13], q[29];
|
||||
cp(pi/131072) q[12], q[29];
|
||||
cp(pi/262144) q[11], q[29];
|
||||
cp(pi/524288) q[10], q[29];
|
||||
cp(pi/1048576) q[9], q[29];
|
||||
cp(pi/2097152) q[8], q[29];
|
||||
cp(pi/4194304) q[7], q[29];
|
||||
cp(pi/8388608) q[6], q[29];
|
||||
cp(pi/16777216) q[5], q[29];
|
||||
cp(pi/33554432) q[4], q[29];
|
||||
cp(pi/67108864) q[3], q[29];
|
||||
cp(pi/134217728) q[2], q[29];
|
||||
cp(pi/268435456) q[1], q[29];
|
||||
cp(pi/536870912) q[0], q[29];
|
||||
h q[28];
|
||||
cp(pi/2) q[27], q[28];
|
||||
cp(pi/4) q[26], q[28];
|
||||
cp(pi/8) q[25], q[28];
|
||||
cp(pi/16) q[24], q[28];
|
||||
cp(pi/32) q[23], q[28];
|
||||
cp(pi/64) q[22], q[28];
|
||||
cp(pi/128) q[21], q[28];
|
||||
cp(pi/256) q[20], q[28];
|
||||
cp(pi/512) q[19], q[28];
|
||||
cp(pi/1024) q[18], q[28];
|
||||
cp(pi/2048) q[17], q[28];
|
||||
cp(pi/4096) q[16], q[28];
|
||||
cp(pi/8192) q[15], q[28];
|
||||
cp(pi/16384) q[14], q[28];
|
||||
cp(pi/32768) q[13], q[28];
|
||||
cp(pi/65536) q[12], q[28];
|
||||
cp(pi/131072) q[11], q[28];
|
||||
cp(pi/262144) q[10], q[28];
|
||||
cp(pi/524288) q[9], q[28];
|
||||
cp(pi/1048576) q[8], q[28];
|
||||
cp(pi/2097152) q[7], q[28];
|
||||
cp(pi/4194304) q[6], q[28];
|
||||
cp(pi/8388608) q[5], q[28];
|
||||
cp(pi/16777216) q[4], q[28];
|
||||
cp(pi/33554432) q[3], q[28];
|
||||
cp(pi/67108864) q[2], q[28];
|
||||
cp(pi/134217728) q[1], q[28];
|
||||
cp(pi/268435456) q[0], q[28];
|
||||
h q[27];
|
||||
cp(pi/2) q[26], q[27];
|
||||
cp(pi/4) q[25], q[27];
|
||||
cp(pi/8) q[24], q[27];
|
||||
cp(pi/16) q[23], q[27];
|
||||
cp(pi/32) q[22], q[27];
|
||||
cp(pi/64) q[21], q[27];
|
||||
cp(pi/128) q[20], q[27];
|
||||
cp(pi/256) q[19], q[27];
|
||||
cp(pi/512) q[18], q[27];
|
||||
cp(pi/1024) q[17], q[27];
|
||||
cp(pi/2048) q[16], q[27];
|
||||
cp(pi/4096) q[15], q[27];
|
||||
cp(pi/8192) q[14], q[27];
|
||||
cp(pi/16384) q[13], q[27];
|
||||
cp(pi/32768) q[12], q[27];
|
||||
cp(pi/65536) q[11], q[27];
|
||||
cp(pi/131072) q[10], q[27];
|
||||
cp(pi/262144) q[9], q[27];
|
||||
cp(pi/524288) q[8], q[27];
|
||||
cp(pi/1048576) q[7], q[27];
|
||||
cp(pi/2097152) q[6], q[27];
|
||||
cp(pi/4194304) q[5], q[27];
|
||||
cp(pi/8388608) q[4], q[27];
|
||||
cp(pi/16777216) q[3], q[27];
|
||||
cp(pi/33554432) q[2], q[27];
|
||||
cp(pi/67108864) q[1], q[27];
|
||||
cp(pi/134217728) q[0], q[27];
|
||||
h q[26];
|
||||
cp(pi/2) q[25], q[26];
|
||||
cp(pi/4) q[24], q[26];
|
||||
cp(pi/8) q[23], q[26];
|
||||
cp(pi/16) q[22], q[26];
|
||||
cp(pi/32) q[21], q[26];
|
||||
cp(pi/64) q[20], q[26];
|
||||
cp(pi/128) q[19], q[26];
|
||||
cp(pi/256) q[18], q[26];
|
||||
cp(pi/512) q[17], q[26];
|
||||
cp(pi/1024) q[16], q[26];
|
||||
cp(pi/2048) q[15], q[26];
|
||||
cp(pi/4096) q[14], q[26];
|
||||
cp(pi/8192) q[13], q[26];
|
||||
cp(pi/16384) q[12], q[26];
|
||||
cp(pi/32768) q[11], q[26];
|
||||
cp(pi/65536) q[10], q[26];
|
||||
cp(pi/131072) q[9], q[26];
|
||||
cp(pi/262144) q[8], q[26];
|
||||
cp(pi/524288) q[7], q[26];
|
||||
cp(pi/1048576) q[6], q[26];
|
||||
cp(pi/2097152) q[5], q[26];
|
||||
cp(pi/4194304) q[4], q[26];
|
||||
cp(pi/8388608) q[3], q[26];
|
||||
cp(pi/16777216) q[2], q[26];
|
||||
cp(pi/33554432) q[1], q[26];
|
||||
cp(pi/67108864) q[0], q[26];
|
||||
h q[25];
|
||||
cp(pi/2) q[24], q[25];
|
||||
cp(pi/4) q[23], q[25];
|
||||
cp(pi/8) q[22], q[25];
|
||||
cp(pi/16) q[21], q[25];
|
||||
cp(pi/32) q[20], q[25];
|
||||
cp(pi/64) q[19], q[25];
|
||||
cp(pi/128) q[18], q[25];
|
||||
cp(pi/256) q[17], q[25];
|
||||
cp(pi/512) q[16], q[25];
|
||||
cp(pi/1024) q[15], q[25];
|
||||
cp(pi/2048) q[14], q[25];
|
||||
cp(pi/4096) q[13], q[25];
|
||||
cp(pi/8192) q[12], q[25];
|
||||
cp(pi/16384) q[11], q[25];
|
||||
cp(pi/32768) q[10], q[25];
|
||||
cp(pi/65536) q[9], q[25];
|
||||
cp(pi/131072) q[8], q[25];
|
||||
cp(pi/262144) q[7], q[25];
|
||||
cp(pi/524288) q[6], q[25];
|
||||
cp(pi/1048576) q[5], q[25];
|
||||
cp(pi/2097152) q[4], q[25];
|
||||
cp(pi/4194304) q[3], q[25];
|
||||
cp(pi/8388608) q[2], q[25];
|
||||
cp(pi/16777216) q[1], q[25];
|
||||
cp(pi/33554432) q[0], q[25];
|
||||
h q[24];
|
||||
cp(pi/2) q[23], q[24];
|
||||
cp(pi/4) q[22], q[24];
|
||||
cp(pi/8) q[21], q[24];
|
||||
cp(pi/16) q[20], q[24];
|
||||
cp(pi/32) q[19], q[24];
|
||||
cp(pi/64) q[18], q[24];
|
||||
cp(pi/128) q[17], q[24];
|
||||
cp(pi/256) q[16], q[24];
|
||||
cp(pi/512) q[15], q[24];
|
||||
cp(pi/1024) q[14], q[24];
|
||||
cp(pi/2048) q[13], q[24];
|
||||
cp(pi/4096) q[12], q[24];
|
||||
cp(pi/8192) q[11], q[24];
|
||||
cp(pi/16384) q[10], q[24];
|
||||
cp(pi/32768) q[9], q[24];
|
||||
cp(pi/65536) q[8], q[24];
|
||||
cp(pi/131072) q[7], q[24];
|
||||
cp(pi/262144) q[6], q[24];
|
||||
cp(pi/524288) q[5], q[24];
|
||||
cp(pi/1048576) q[4], q[24];
|
||||
cp(pi/2097152) q[3], q[24];
|
||||
cp(pi/4194304) q[2], q[24];
|
||||
cp(pi/8388608) q[1], q[24];
|
||||
cp(pi/16777216) q[0], q[24];
|
||||
h q[23];
|
||||
cp(pi/2) q[22], q[23];
|
||||
cp(pi/4) q[21], q[23];
|
||||
cp(pi/8) q[20], q[23];
|
||||
cp(pi/16) q[19], q[23];
|
||||
cp(pi/32) q[18], q[23];
|
||||
cp(pi/64) q[17], q[23];
|
||||
cp(pi/128) q[16], q[23];
|
||||
cp(pi/256) q[15], q[23];
|
||||
cp(pi/512) q[14], q[23];
|
||||
cp(pi/1024) q[13], q[23];
|
||||
cp(pi/2048) q[12], q[23];
|
||||
cp(pi/4096) q[11], q[23];
|
||||
cp(pi/8192) q[10], q[23];
|
||||
cp(pi/16384) q[9], q[23];
|
||||
cp(pi/32768) q[8], q[23];
|
||||
cp(pi/65536) q[7], q[23];
|
||||
cp(pi/131072) q[6], q[23];
|
||||
cp(pi/262144) q[5], q[23];
|
||||
cp(pi/524288) q[4], q[23];
|
||||
cp(pi/1048576) q[3], q[23];
|
||||
cp(pi/2097152) q[2], q[23];
|
||||
cp(pi/4194304) q[1], q[23];
|
||||
cp(pi/8388608) q[0], q[23];
|
||||
h q[22];
|
||||
cp(pi/2) q[21], q[22];
|
||||
cp(pi/4) q[20], q[22];
|
||||
cp(pi/8) q[19], q[22];
|
||||
cp(pi/16) q[18], q[22];
|
||||
cp(pi/32) q[17], q[22];
|
||||
cp(pi/64) q[16], q[22];
|
||||
cp(pi/128) q[15], q[22];
|
||||
cp(pi/256) q[14], q[22];
|
||||
cp(pi/512) q[13], q[22];
|
||||
cp(pi/1024) q[12], q[22];
|
||||
cp(pi/2048) q[11], q[22];
|
||||
cp(pi/4096) q[10], q[22];
|
||||
cp(pi/8192) q[9], q[22];
|
||||
cp(pi/16384) q[8], q[22];
|
||||
cp(pi/32768) q[7], q[22];
|
||||
cp(pi/65536) q[6], q[22];
|
||||
cp(pi/131072) q[5], q[22];
|
||||
cp(pi/262144) q[4], q[22];
|
||||
cp(pi/524288) q[3], q[22];
|
||||
cp(pi/1048576) q[2], q[22];
|
||||
cp(pi/2097152) q[1], q[22];
|
||||
cp(pi/4194304) q[0], q[22];
|
||||
h q[21];
|
||||
cp(pi/2) q[20], q[21];
|
||||
cp(pi/4) q[19], q[21];
|
||||
cp(pi/8) q[18], q[21];
|
||||
cp(pi/16) q[17], q[21];
|
||||
cp(pi/32) q[16], q[21];
|
||||
cp(pi/64) q[15], q[21];
|
||||
cp(pi/128) q[14], q[21];
|
||||
cp(pi/256) q[13], q[21];
|
||||
cp(pi/512) q[12], q[21];
|
||||
cp(pi/1024) q[11], q[21];
|
||||
cp(pi/2048) q[10], q[21];
|
||||
cp(pi/4096) q[9], q[21];
|
||||
cp(pi/8192) q[8], q[21];
|
||||
cp(pi/16384) q[7], q[21];
|
||||
cp(pi/32768) q[6], q[21];
|
||||
cp(pi/65536) q[5], q[21];
|
||||
cp(pi/131072) q[4], q[21];
|
||||
cp(pi/262144) q[3], q[21];
|
||||
cp(pi/524288) q[2], q[21];
|
||||
cp(pi/1048576) q[1], q[21];
|
||||
cp(pi/2097152) q[0], q[21];
|
||||
h q[20];
|
||||
cp(pi/2) q[19], q[20];
|
||||
cp(pi/4) q[18], q[20];
|
||||
cp(pi/8) q[17], q[20];
|
||||
cp(pi/16) q[16], q[20];
|
||||
cp(pi/32) q[15], q[20];
|
||||
cp(pi/64) q[14], q[20];
|
||||
cp(pi/128) q[13], q[20];
|
||||
cp(pi/256) q[12], q[20];
|
||||
cp(pi/512) q[11], q[20];
|
||||
cp(pi/1024) q[10], q[20];
|
||||
cp(pi/2048) q[9], q[20];
|
||||
cp(pi/4096) q[8], q[20];
|
||||
cp(pi/8192) q[7], q[20];
|
||||
cp(pi/16384) q[6], q[20];
|
||||
cp(pi/32768) q[5], q[20];
|
||||
cp(pi/65536) q[4], q[20];
|
||||
cp(pi/131072) q[3], q[20];
|
||||
cp(pi/262144) q[2], q[20];
|
||||
cp(pi/524288) q[1], q[20];
|
||||
cp(pi/1048576) q[0], q[20];
|
||||
h q[19];
|
||||
cp(pi/2) q[18], q[19];
|
||||
cp(pi/4) q[17], q[19];
|
||||
cp(pi/8) q[16], q[19];
|
||||
cp(pi/16) q[15], q[19];
|
||||
cp(pi/32) q[14], q[19];
|
||||
cp(pi/64) q[13], q[19];
|
||||
cp(pi/128) q[12], q[19];
|
||||
cp(pi/256) q[11], q[19];
|
||||
cp(pi/512) q[10], q[19];
|
||||
cp(pi/1024) q[9], q[19];
|
||||
cp(pi/2048) q[8], q[19];
|
||||
cp(pi/4096) q[7], q[19];
|
||||
cp(pi/8192) q[6], q[19];
|
||||
cp(pi/16384) q[5], q[19];
|
||||
cp(pi/32768) q[4], q[19];
|
||||
cp(pi/65536) q[3], q[19];
|
||||
cp(pi/131072) q[2], q[19];
|
||||
cp(pi/262144) q[1], q[19];
|
||||
cp(pi/524288) q[0], q[19];
|
||||
h q[18];
|
||||
cp(pi/2) q[17], q[18];
|
||||
cp(pi/4) q[16], q[18];
|
||||
cp(pi/8) q[15], q[18];
|
||||
cp(pi/16) q[14], q[18];
|
||||
cp(pi/32) q[13], q[18];
|
||||
cp(pi/64) q[12], q[18];
|
||||
cp(pi/128) q[11], q[18];
|
||||
cp(pi/256) q[10], q[18];
|
||||
cp(pi/512) q[9], q[18];
|
||||
cp(pi/1024) q[8], q[18];
|
||||
cp(pi/2048) q[7], q[18];
|
||||
cp(pi/4096) q[6], q[18];
|
||||
cp(pi/8192) q[5], q[18];
|
||||
cp(pi/16384) q[4], q[18];
|
||||
cp(pi/32768) q[3], q[18];
|
||||
cp(pi/65536) q[2], q[18];
|
||||
cp(pi/131072) q[1], q[18];
|
||||
cp(pi/262144) q[0], q[18];
|
||||
h q[17];
|
||||
cp(pi/2) q[16], q[17];
|
||||
cp(pi/4) q[15], q[17];
|
||||
cp(pi/8) q[14], q[17];
|
||||
cp(pi/16) q[13], q[17];
|
||||
cp(pi/32) q[12], q[17];
|
||||
cp(pi/64) q[11], q[17];
|
||||
cp(pi/128) q[10], q[17];
|
||||
cp(pi/256) q[9], q[17];
|
||||
cp(pi/512) q[8], q[17];
|
||||
cp(pi/1024) q[7], q[17];
|
||||
cp(pi/2048) q[6], q[17];
|
||||
cp(pi/4096) q[5], q[17];
|
||||
cp(pi/8192) q[4], q[17];
|
||||
cp(pi/16384) q[3], q[17];
|
||||
cp(pi/32768) q[2], q[17];
|
||||
cp(pi/65536) q[1], q[17];
|
||||
cp(pi/131072) q[0], q[17];
|
||||
h q[16];
|
||||
cp(pi/2) q[15], q[16];
|
||||
cp(pi/4) q[14], q[16];
|
||||
cp(pi/8) q[13], q[16];
|
||||
cp(pi/16) q[12], q[16];
|
||||
cp(pi/32) q[11], q[16];
|
||||
cp(pi/64) q[10], q[16];
|
||||
cp(pi/128) q[9], q[16];
|
||||
cp(pi/256) q[8], q[16];
|
||||
cp(pi/512) q[7], q[16];
|
||||
cp(pi/1024) q[6], q[16];
|
||||
cp(pi/2048) q[5], q[16];
|
||||
cp(pi/4096) q[4], q[16];
|
||||
cp(pi/8192) q[3], q[16];
|
||||
cp(pi/16384) q[2], q[16];
|
||||
cp(pi/32768) q[1], q[16];
|
||||
cp(pi/65536) q[0], q[16];
|
||||
h q[15];
|
||||
cp(pi/2) q[14], q[15];
|
||||
cp(pi/4) q[13], q[15];
|
||||
cp(pi/8) q[12], q[15];
|
||||
cp(pi/16) q[11], q[15];
|
||||
cp(pi/32) q[10], q[15];
|
||||
cp(pi/64) q[9], q[15];
|
||||
cp(pi/128) q[8], q[15];
|
||||
cp(pi/256) q[7], q[15];
|
||||
cp(pi/512) q[6], q[15];
|
||||
cp(pi/1024) q[5], q[15];
|
||||
cp(pi/2048) q[4], q[15];
|
||||
cp(pi/4096) q[3], q[15];
|
||||
cp(pi/8192) q[2], q[15];
|
||||
cp(pi/16384) q[1], q[15];
|
||||
cp(pi/32768) q[0], q[15];
|
||||
h q[14];
|
||||
cp(pi/2) q[13], q[14];
|
||||
cp(pi/4) q[12], q[14];
|
||||
cp(pi/8) q[11], q[14];
|
||||
cp(pi/16) q[10], q[14];
|
||||
cp(pi/32) q[9], q[14];
|
||||
cp(pi/64) q[8], q[14];
|
||||
cp(pi/128) q[7], q[14];
|
||||
cp(pi/256) q[6], q[14];
|
||||
cp(pi/512) q[5], q[14];
|
||||
cp(pi/1024) q[4], q[14];
|
||||
cp(pi/2048) q[3], q[14];
|
||||
cp(pi/4096) q[2], q[14];
|
||||
cp(pi/8192) q[1], q[14];
|
||||
cp(pi/16384) q[0], q[14];
|
||||
h q[13];
|
||||
cp(pi/2) q[12], q[13];
|
||||
cp(pi/4) q[11], q[13];
|
||||
cp(pi/8) q[10], q[13];
|
||||
cp(pi/16) q[9], q[13];
|
||||
cp(pi/32) q[8], q[13];
|
||||
cp(pi/64) q[7], q[13];
|
||||
cp(pi/128) q[6], q[13];
|
||||
cp(pi/256) q[5], q[13];
|
||||
cp(pi/512) q[4], q[13];
|
||||
cp(pi/1024) q[3], q[13];
|
||||
cp(pi/2048) q[2], q[13];
|
||||
cp(pi/4096) q[1], q[13];
|
||||
cp(pi/8192) q[0], q[13];
|
||||
h q[12];
|
||||
cp(pi/2) q[11], q[12];
|
||||
cp(pi/4) q[10], q[12];
|
||||
cp(pi/8) q[9], q[12];
|
||||
cp(pi/16) q[8], q[12];
|
||||
cp(pi/32) q[7], q[12];
|
||||
cp(pi/64) q[6], q[12];
|
||||
cp(pi/128) q[5], q[12];
|
||||
cp(pi/256) q[4], q[12];
|
||||
cp(pi/512) q[3], q[12];
|
||||
cp(pi/1024) q[2], q[12];
|
||||
cp(pi/2048) q[1], q[12];
|
||||
cp(pi/4096) q[0], q[12];
|
||||
h q[11];
|
||||
cp(pi/2) q[10], q[11];
|
||||
cp(pi/4) q[9], q[11];
|
||||
cp(pi/8) q[8], q[11];
|
||||
cp(pi/16) q[7], q[11];
|
||||
cp(pi/32) q[6], q[11];
|
||||
cp(pi/64) q[5], q[11];
|
||||
cp(pi/128) q[4], q[11];
|
||||
cp(pi/256) q[3], q[11];
|
||||
cp(pi/512) q[2], q[11];
|
||||
cp(pi/1024) q[1], q[11];
|
||||
cp(pi/2048) q[0], q[11];
|
||||
h q[10];
|
||||
cp(pi/2) q[9], q[10];
|
||||
cp(pi/4) q[8], q[10];
|
||||
cp(pi/8) q[7], q[10];
|
||||
cp(pi/16) q[6], q[10];
|
||||
cp(pi/32) q[5], q[10];
|
||||
cp(pi/64) q[4], q[10];
|
||||
cp(pi/128) q[3], q[10];
|
||||
cp(pi/256) q[2], q[10];
|
||||
cp(pi/512) q[1], q[10];
|
||||
cp(pi/1024) q[0], q[10];
|
||||
h q[9];
|
||||
cp(pi/2) q[8], q[9];
|
||||
cp(pi/4) q[7], q[9];
|
||||
cp(pi/8) q[6], q[9];
|
||||
cp(pi/16) q[5], q[9];
|
||||
cp(pi/32) q[4], q[9];
|
||||
cp(pi/64) q[3], q[9];
|
||||
cp(pi/128) q[2], q[9];
|
||||
cp(pi/256) q[1], q[9];
|
||||
cp(pi/512) q[0], q[9];
|
||||
h q[8];
|
||||
cp(pi/2) q[7], q[8];
|
||||
cp(pi/4) q[6], q[8];
|
||||
cp(pi/8) q[5], q[8];
|
||||
cp(pi/16) q[4], q[8];
|
||||
cp(pi/32) q[3], q[8];
|
||||
cp(pi/64) q[2], q[8];
|
||||
cp(pi/128) q[1], q[8];
|
||||
cp(pi/256) q[0], q[8];
|
||||
h q[7];
|
||||
cp(pi/2) q[6], q[7];
|
||||
cp(pi/4) q[5], q[7];
|
||||
cp(pi/8) q[4], q[7];
|
||||
cp(pi/16) q[3], q[7];
|
||||
cp(pi/32) q[2], q[7];
|
||||
cp(pi/64) q[1], q[7];
|
||||
cp(pi/128) q[0], q[7];
|
||||
h q[6];
|
||||
cp(pi/2) q[5], q[6];
|
||||
cp(pi/4) q[4], q[6];
|
||||
cp(pi/8) q[3], q[6];
|
||||
cp(pi/16) q[2], q[6];
|
||||
cp(pi/32) q[1], q[6];
|
||||
cp(pi/64) q[0], q[6];
|
||||
h q[5];
|
||||
cp(pi/2) q[4], q[5];
|
||||
cp(pi/4) q[3], q[5];
|
||||
cp(pi/8) q[2], q[5];
|
||||
cp(pi/16) q[1], q[5];
|
||||
cp(pi/32) q[0], q[5];
|
||||
h q[4];
|
||||
cp(pi/2) q[3], q[4];
|
||||
cp(pi/4) q[2], q[4];
|
||||
cp(pi/8) q[1], q[4];
|
||||
cp(pi/16) q[0], q[4];
|
||||
h q[3];
|
||||
cp(pi/2) q[2], q[3];
|
||||
cp(pi/4) q[1], q[3];
|
||||
cp(pi/8) q[0], q[3];
|
||||
h q[2];
|
||||
cp(pi/2) q[1], q[2];
|
||||
cp(pi/4) q[0], q[2];
|
||||
h q[1];
|
||||
cp(pi/2) q[0], q[1];
|
||||
h q[0];
|
||||
swap q[0], q[29];
|
||||
swap q[1], q[28];
|
||||
swap q[2], q[27];
|
||||
swap q[3], q[26];
|
||||
swap q[4], q[25];
|
||||
swap q[5], q[24];
|
||||
swap q[6], q[23];
|
||||
swap q[7], q[22];
|
||||
swap q[8], q[21];
|
||||
swap q[9], q[20];
|
||||
swap q[10], q[19];
|
||||
swap q[11], q[18];
|
||||
swap q[12], q[17];
|
||||
swap q[13], q[16];
|
||||
swap q[14], q[15];
|
||||
|
|
@ -15,13 +15,17 @@
|
|||
"reason": "An observable on Pauli targets rather than measurement records.",
|
||||
"text": "R 0 1 2\nH 0\nCX 0 1 1 2\nZ_ERROR(0.01) 1\nOBSERVABLE_INCLUDE(0) X0 X1 X2\n"
|
||||
},
|
||||
"qec_refused_correlated": {
|
||||
"reason": "A correlated error, which the frame simulator does not implement: it must be refused with its line.",
|
||||
"text": "R 0 1\nE(0.01) X0 X1\nM 0 1\nDETECTOR rec[-1]\n"
|
||||
"qec_correlated": {
|
||||
"reason": "A correlated error, with a Y among its targets: one mechanism with every symptom, and the targets flipping together.",
|
||||
"text": "R 0 1 2\nE(0.01) X0 Y1\nE(0.02) X2\nM 0 1 2\nDETECTOR rec[-3]\nDETECTOR rec[-2]\nDETECTOR rec[-1]\n"
|
||||
},
|
||||
"qec_refused_pauli_channel": {
|
||||
"reason": "A general Pauli channel, which it does not implement either.",
|
||||
"text": "R 0\nPAULI_CHANNEL_1(0.01, 0.02, 0.03) 0\nM 0\nDETECTOR rec[-1]\n"
|
||||
"qec_pauli_channel": {
|
||||
"reason": "A general Pauli channel: exclusive outcomes, written as independent flips at the rates that give the same law. The model must match mechanism by mechanism, and every detector's rate must follow it.",
|
||||
"text": "R 0\nRX 1\nPAULI_CHANNEL_1(0.01, 0.02, 0.03) 0 1\nM 0\nMX 1\nDETECTOR rec[-2]\nDETECTOR rec[-1]\n"
|
||||
},
|
||||
"qec_refused_channel_with_no_independent_form": {
|
||||
"reason": "A Pauli channel whose exclusive outcomes no independent flips can give (Z's rate would be below zero): it must be refused with its line, never approximated in silence.",
|
||||
"text": "R 0\nPAULI_CHANNEL_1(0.1, 0.1, 0) 0\nM 0\nDETECTOR rec[-1]\n"
|
||||
},
|
||||
"qec_refused_random_detector": {
|
||||
"reason": "A detector that is random without noise is no detector: it must be refused.",
|
||||
|
|
|
|||
3619
wai/quantum-cross-simulator/results/before-after-macos-arm64.json
Normal file
3619
wai/quantum-cross-simulator/results/before-after-macos-arm64.json
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
32
wai/quantum-cross-simulator/tools/before_after.py
Normal file
32
wai/quantum-cross-simulator/tools/before_after.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env python3
|
||||
"""One host's timings of two builds of the engine, side by side.
|
||||
|
||||
before_after.py BEFORE.json AFTER.json OUT.json
|
||||
|
||||
BEFORE and AFTER are tools/perf.py's output on one host with WAI_CROSS_SIMULATOR naming each
|
||||
build's driver, run one straight after the other. Standard library only.
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
before, after = (json.load(open(p)) for p in sys.argv[1:3])
|
||||
# The host's identity, not its load at the moment each run began.
|
||||
same = lambda h: {k: v for k, v in h.items() if k != "load_average"}
|
||||
if same(before["host"]) != same(after["host"]) or before["repeat"] != after["repeat"]:
|
||||
sys.exit("before_after.py: two hosts, or two repeat counts: these timings do not compare")
|
||||
index = {(r["case"], r["engine"], r["threads"]): r for r in before["rows"]}
|
||||
rows = []
|
||||
for r in after["rows"]:
|
||||
was = index.get((r["case"], r["engine"], r["threads"]))
|
||||
if was is not None:
|
||||
rows.append({"case": r["case"], "what": r["what"], "engine": r["engine"], "threads": r["threads"], "before": {k: v for k, v in was.items() if k not in ("case", "what", "engine", "threads")}, "after": {k: v for k, v in r.items() if k not in ("case", "what", "engine", "threads")}})
|
||||
out = {"about": "One host's timings of two builds of the engine under test, one straight after the other: tools/perf.py run on each, tools/before_after.py putting them side by side.", "timers": after.get("timers"), "host": after["host"], "versions": after["versions"], "before": before["engine_under_test"], "after": after["engine_under_test"], "repeat": after["repeat"], "rows": rows}
|
||||
with open(sys.argv[3], "w") as f:
|
||||
json.dump(out, f, indent=1)
|
||||
f.write("\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -551,6 +551,11 @@ def check_state_entry(e, runner, rep, engines):
|
|||
c = rep.add(e["id"], eng, quantity, measured, bound, measured <= bound, note=why)
|
||||
c["strict"] = d
|
||||
c["global_phase"] = phase
|
||||
# The phase the method's result states: with it, the state is the reference's, which writes
|
||||
# each gate as method statevector does, nothing fitted.
|
||||
turn = complex(math.cos(ans.get("global_phase", 0.0)), math.sin(ans.get("global_phase", 0.0)))
|
||||
stated = exact.distance(ref.re, ref.im, *[exact.ints_from_floats([float(x) for x in part]) for part in ((vec * turn).real, (vec * turn).imag)])[0]
|
||||
rep.add(e["id"], eng, "distance from the exact state, with the global phase its result states", stated, bound + 8 * tol.U_DOUBLE, stated <= bound + 8 * tol.U_DOUBLE)
|
||||
exact_ops = ans["ops"]
|
||||
exact_answer(rep, e, n, ans, want)
|
||||
if e.get("extension_figure"):
|
||||
|
|
@ -577,16 +582,12 @@ def check_state_entry(e, runner, rep, engines):
|
|||
no_answer(rep, e["id"], "wai.auto", ans, "chosen amplitudes through the job path")
|
||||
return
|
||||
planned = ans.get("planned")
|
||||
# The bit-exact method keeps no global phase (it lowers rz to a phase gate, and so on): its
|
||||
# amplitudes are held up to one phase, the one that aligns the largest of them; where the
|
||||
# phase is the result, they are held with it.
|
||||
# The bit-exact method lowers rz to a phase gate, and so on; its result states the phase that
|
||||
# drops, and its amplitudes are held to the reference's with that phase, nothing fitted.
|
||||
turn = 1
|
||||
if planned == "exact" and not strict:
|
||||
top = max(picks, key=lambda i: ref.re[i] ** 2 + ref.im[i] ** 2)
|
||||
got = complex(*ans["amplitudes"][format(top, "0%db" % n)])
|
||||
want_top = complex(ref.re[top] / 2.0**exact.PREC, ref.im[top] / 2.0**exact.PREC)
|
||||
if abs(got) > 0:
|
||||
turn = (want_top / got) / abs(want_top / got)
|
||||
if planned == "exact":
|
||||
stated_phase = ((ans.get("accuracy") or {}).get("global_phase") or {}).get("radians", 0.0)
|
||||
turn = complex(math.cos(stated_phase), math.sin(stated_phase))
|
||||
worst = 0.0
|
||||
for b in bits:
|
||||
a = complex(*ans["amplitudes"][b]) * turn
|
||||
|
|
@ -599,7 +600,7 @@ def check_state_entry(e, runner, rep, engines):
|
|||
bound = tol.fixed_point_bound(n, exact_ops) + 4 * tol.U_DOUBLE
|
||||
else:
|
||||
bound = tol.double_state(tol.gate_count(e["steps"]), n, width=3)
|
||||
rep.add(e["id"], "wai.auto", "chosen amplitudes through the job path, worst error" + (", modulo a global phase" if turn != 1 else ""), worst, bound, worst <= bound, note="planned %s" % planned)
|
||||
rep.add(e["id"], "wai.auto", "chosen amplitudes through the job path, worst error" + (", with the global phase the result states" if planned == "exact" else ""), worst, bound, worst <= bound, note="planned %s" % planned)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ stabilizers
|
||||
|
|
@ -880,7 +881,8 @@ def check_wide_entry(e, runner, rep):
|
|||
engines = ["wai.mps", "wai.tensor_network", "aer.mps", "quimb.mps", "quimb.tensor_network"]
|
||||
if n <= 26:
|
||||
engines += ["wai.statevector", "aer.statevector"]
|
||||
jobs = {eng: {"id": e["id"], "kind": "amplitudes", "n": n, "steps": e["steps"], "bitstrings": bits, "memory_gb": 24} for eng in engines}
|
||||
engines = e.get("only_engines") or engines
|
||||
jobs = {eng: {"id": e["id"], "kind": "amplitudes", "n": n, "steps": e["steps"], "bitstrings": bits, "memory_gb": 24, "max_bond": e.get("max_bond")} for eng in engines}
|
||||
got = {}
|
||||
gates = tol.gate_count(e["steps"])
|
||||
smallest = min((math.hypot(*ref[b]) / exact.ONE for b in bits if ref and any(ref[b])), default=None) if ref else None
|
||||
|
|
@ -974,7 +976,12 @@ def check_qec_entry(q, text, runner, rep, qec_plan):
|
|||
if not answered(rep, q["id"], "wai.qec"):
|
||||
return
|
||||
if not b.get("ok"):
|
||||
rep.add(q["id"], "stim.tableau", "detector error model", None, None, False, note="refused here, run by the engine under test: %s" % str(words(b))[:300])
|
||||
# A channel the other engine gives no exact model of (a general Pauli channel): its
|
||||
# sampler still holds the engine under test's model, by the rates below.
|
||||
rep.not_run(q["id"], "stim.tableau", "detector error model", "gives no model of this circuit, which the engine under test models: %s" % str(words(b))[:300])
|
||||
shots = 200000 if q["tier"] == "ci" else 1000000
|
||||
sj = {eng: {"id": q["id"], "kind": "qec_sample", "text": text, "shots": shots, "seed": 5} for eng in ("wai.qec", "stim.tableau")}
|
||||
qec_plan.append((q, merged(a["mechanisms"]), a["detectors"], a["observables"], run_all(runner, sj), shots))
|
||||
return
|
||||
rep.add(q["id"], "wai.qec ~ stim.tableau", "detectors and observables", [a["detectors"], a["observables"]], [b["detectors"], b["observables"]], [a["detectors"], a["observables"]] == [b["detectors"], b["observables"]])
|
||||
ma, mb = merged(a["mechanisms"]), merged(b["mechanisms"])
|
||||
|
|
@ -1012,7 +1019,7 @@ def text_noise_sites(text):
|
|||
stack.pop()
|
||||
continue
|
||||
m = re.match(r"([A-Z_0-9]+)(\[[^\]]*\])?\(([^)]*)\)\s*(.*)", s)
|
||||
if m and (m.group(1).endswith("ERROR") or m.group(1).startswith("DEPOLARIZE") or m.group(1) in ("M", "MX", "MR", "MRX", "MY")):
|
||||
if m and (m.group(1).endswith("ERROR") or m.group(1).startswith(("DEPOLARIZE", "PAULI_CHANNEL")) or m.group(1) in ("E", "M", "MX", "MR", "MRX", "MY")):
|
||||
total += stack[-1] * max(len(m.group(4).split()), 1)
|
||||
return total
|
||||
|
||||
|
|
@ -1187,7 +1194,7 @@ def manifest_check(es, rep):
|
|||
rep.add(e["id"], "corpus.py", "the committed program equals the generator's", False, True, False)
|
||||
|
||||
|
||||
CI_HAND = ("qec_inverted_and_tagged", "qec_refused_correlated", "qec_refused_random_detector")
|
||||
CI_HAND = ("qec_inverted_and_tagged", "qec_correlated", "qec_refused_random_detector")
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
|||
|
|
@ -402,6 +402,8 @@ def entries():
|
|||
add("mps_ghz_n%d" % n, "wide_mps", {"n": n}, "Bond dimension 2 at any width: the amplitudes of the two outcomes, in closed form.", n, ghz_steps(n), "amplitudes", wide_kind="ghz", tier="ci" if n == 30 else "full")
|
||||
for n, x in ((30, 715827883), (40, 366503875925)):
|
||||
add("mps_qft_n%d" % n, "wide_mps", {"n": n, "x": x}, "The QFT of a basis state is a product state, reached through modest entanglement. Chosen amplitudes in closed form.", n, basis_prep(n, x) + qft_steps(n), "amplitudes", wide_kind="qft", qft_x=x)
|
||||
# The bond asked for is not the bond reached: at 256, past what an estimate at that bond admits.
|
||||
add("mps_qft_n30_bond256", "wide_mps", {"n": 30, "x": 715827883, "max_bond": 256}, "The 30-qubit QFT at a bond far above what it reaches: a job naming the method runs under a meter of the bond it holds, and stays exact.", 30, basis_prep(30, 715827883) + qft_steps(30), "amplitudes", wide_kind="qft", qft_x=715827883, max_bond=256, only_engines=["wai.mps"])
|
||||
add("tn_shallow_n26", "wide_tn", {"n": 26, "depth": 5, "seed": 41}, "A shallow rotation brickwork: amplitudes by contraction, against matrix product states and a state vector.", 26, random_rotations(26, 5, 41), "wide_pairwise")
|
||||
add("tn_mirror_n40", "wide_tn", {"n": 40, "depth": 3, "seed": 42}, "A shallow mirror circuit, too wide for a state vector here: the amplitude of |0…0> is exactly 1.", 40, random_dyadic(40, 3, 42, 16) + inverse_steps(random_dyadic(40, 3, 42, 16)), "wide_mirror", tier="ci")
|
||||
|
||||
|
|
@ -473,9 +475,7 @@ def reader_class(e):
|
|||
# must refuse (accepting fails the run); "may": a refusal with these words is recorded as one,
|
||||
# and an answer is checked like any other.
|
||||
QEC_REFUSALS = [
|
||||
{"entry": "color_d3", "engine": "wai.qec", "must": "may", "text": "unsupported instruction 'C_XYZ'", "reason": "the frame simulator does not implement C_XYZ"},
|
||||
{"entry": "qec_refused_correlated", "engine": "wai.qec", "must": "may", "text": "unsupported instruction 'E'", "reason": "the frame simulator does not implement correlated errors"},
|
||||
{"entry": "qec_refused_pauli_channel", "engine": "wai.qec", "must": "may", "text": "unsupported instruction 'PAULI_CHANNEL_1'", "reason": "the frame simulator does not implement general Pauli channels"},
|
||||
{"entry": "qec_refused_channel_with_no_independent_form", "engine": "wai.qec", "must": "refuse", "text": "no form as independent", "reason": "a Pauli channel no independent flips can give is refused, never approximated"},
|
||||
{"entry": "qec_refused_random_detector", "engine": "wai.qec", "must": "refuse", "text": "is not deterministic", "reason": "a detector that is random without noise is no detector"},
|
||||
]
|
||||
|
||||
|
|
@ -509,7 +509,7 @@ def qec_entries():
|
|||
for d in (3, 9, 25):
|
||||
add("repetition_d%d" % d, "repetition_code:memory", d, d, 0.005, "A line of data qubits: the simplest model, with long detector chains at d = 25.")
|
||||
add("surface_z_d5_noiseless", "surface_code:rotated_memory_z", 5, 5, 0.0, "No noise at all: every detector and the observable are silent on every shot, and the model is empty.", tier="ci")
|
||||
add("color_d3", "color_code:memory_xyz", 3, 3, 0.002, "A colour code with C_XYZ gates: an instruction the frame simulator does not implement must be refused with its line, never skipped.")
|
||||
add("color_d3", "color_code:memory_xyz", 3, 3, 0.002, "A colour code, whose syndrome cycle turns the axes with C_XYZ gates: the model must match mechanism by mechanism, and every detector's rate must follow it.")
|
||||
return out
|
||||
|
||||
|
||||
|
|
@ -546,15 +546,27 @@ CX 0 1 1 2
|
|||
Z_ERROR(0.01) 1
|
||||
OBSERVABLE_INCLUDE(0) X0 X1 X2
|
||||
"""),
|
||||
"qec_refused_correlated": ("A correlated error, which the frame simulator does not implement: it must be refused with its line.", """\
|
||||
R 0 1
|
||||
E(0.01) X0 X1
|
||||
M 0 1
|
||||
"qec_correlated": ("A correlated error, with a Y among its targets: one mechanism with every symptom, and the targets flipping together.", """\
|
||||
R 0 1 2
|
||||
E(0.01) X0 Y1
|
||||
E(0.02) X2
|
||||
M 0 1 2
|
||||
DETECTOR rec[-3]
|
||||
DETECTOR rec[-2]
|
||||
DETECTOR rec[-1]
|
||||
"""),
|
||||
"qec_refused_pauli_channel": ("A general Pauli channel, which it does not implement either.", """\
|
||||
"qec_pauli_channel": ("A general Pauli channel: exclusive outcomes, written as independent flips at the rates that give the same law. The model must match mechanism by mechanism, and every detector's rate must follow it.", """\
|
||||
R 0
|
||||
PAULI_CHANNEL_1(0.01, 0.02, 0.03) 0
|
||||
RX 1
|
||||
PAULI_CHANNEL_1(0.01, 0.02, 0.03) 0 1
|
||||
M 0
|
||||
MX 1
|
||||
DETECTOR rec[-2]
|
||||
DETECTOR rec[-1]
|
||||
"""),
|
||||
"qec_refused_channel_with_no_independent_form": ("A Pauli channel whose exclusive outcomes no independent flips can give (Z's rate would be below zero): it must be refused with its line, never approximated in silence.", """\
|
||||
R 0
|
||||
PAULI_CHANNEL_1(0.1, 0.1, 0) 0
|
||||
M 0
|
||||
DETECTOR rec[-1]
|
||||
"""),
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ def rounded_angles(steps):
|
|||
return out
|
||||
|
||||
|
||||
def wai_state(job, method):
|
||||
def wai_state(job, method, controls=None):
|
||||
from corpus import qasm_of
|
||||
|
||||
steps = gates_only(job)
|
||||
|
|
@ -165,9 +165,9 @@ def wai_state(job, method):
|
|||
q = qasm_of({"n": job["n"], "bits": 0, "steps": steps})
|
||||
if job.get("perf"):
|
||||
# Timed: the state is made and not written.
|
||||
return wai_driver("state", {"qasm": q, "method": method, "fusion": job.get("fusion", 3), "ops": False}, job.get("inject"))
|
||||
return wai_driver("state", {"qasm": q, "method": method, "fusion": job.get("fusion", 3), "ops": False, "controls": controls}, job.get("inject"))
|
||||
raw = job["state_out"] + ".raw"
|
||||
a = wai_driver("state", {"qasm": q, "method": method, "state_out": raw, "fusion": job.get("fusion", 3), "ops": method == "exact"}, job.get("inject"))
|
||||
a = wai_driver("state", {"qasm": q, "method": method, "state_out": raw, "fusion": job.get("fusion", 3), "ops": method == "exact", "controls": controls}, job.get("inject"))
|
||||
if not a["ok"]:
|
||||
return a
|
||||
import numpy as np
|
||||
|
|
@ -756,6 +756,9 @@ def stim_generated(job):
|
|||
|
||||
ADAPTERS = {
|
||||
"wai.exact": lambda j: wai_state(j, "exact") if j["kind"] == "state" else wai_job(j, "exact"),
|
||||
# The bit-exact method asked to hold a gate under many controls whole ("controls": "whole"):
|
||||
# other bits than its default, which runs standard gates.
|
||||
"wai.exact_whole": lambda j: wai_state(j, "exact", "whole") if j["kind"] == "state" else wai_job(dict(j, options={"controls": "whole"}), "exact"),
|
||||
"wai.statevector": lambda j: wai_state(j, "statevector") if j["kind"] == "state" else wai_job(j, "statevector"),
|
||||
"wai.stabilizer": lambda j: wai_stabilizers(j) if j["kind"] == "stabilizers" else wai_job(j, "stabilizer"),
|
||||
"wai.auto": lambda j: wai_job(j, "auto"),
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import corpus # noqa: E402
|
|||
# available_parallelism), "one" a method with no threads of its own, "given" the count passed in
|
||||
# its own setting, and "not set" an engine the adapter gives no thread setting at all.
|
||||
THREADS = {
|
||||
"wai.statevector": "all", "wai.tensor_network": "all", "wai.exact": "one", "wai.stabilizer": "one", "wai.qec": "one", "wai.mps": "one",
|
||||
"wai.statevector": "all", "wai.tensor_network": "all", "wai.exact": "one", "wai.exact_whole": "one", "wai.stabilizer": "one", "wai.qec": "one", "wai.mps": "one",
|
||||
"aer.statevector": "given", "aer.stabilizer": "given", "aer.mps": "given", "qsim.statevector": "given",
|
||||
"ddsim.statevector": "not set", "stim.tableau": "not set", "quimb.dense": "not set", "quimb.mps": "not set", "quimb.tensor_network": "not set",
|
||||
}
|
||||
|
|
@ -73,8 +73,13 @@ def cases(quick):
|
|||
if n <= 20:
|
||||
engines.append("quimb.dense")
|
||||
out.append({"id": "rotations_n%d" % n, "what": "20 layers of arbitrary U gates on a brickwork of cx, the whole state", "kind": "state", "n": n, "steps": steps, "engines": engines})
|
||||
for n in (10, 14):
|
||||
out.append({"id": "grover_n%d" % n, "what": "two Grover iterations on %d qubits: four Z gates under %d controls, the whole state" % (n, n - 1), "kind": "state", "n": n, "steps": corpus.grover_steps(n, (1 << n) // 3, 2), "engines": ["wai.statevector", "wai.exact", "aer.statevector", "qsim.statevector", "ddsim.statevector"]})
|
||||
# Past 16 the gates under n - 1 controls stand whole, or (at 17) past what a program may be
|
||||
# in standard gates: a width only the state vectors that hold them whole reach.
|
||||
for n in (10, 14, 16, 20):
|
||||
# The bit-exact method runs standard gates unless asked (its bits are theirs): to 14
|
||||
# qubits as it runs by default, and at every width asked to hold the gate whole.
|
||||
engines = ["wai.statevector"] + (["wai.exact"] if n <= 14 else []) + ["wai.exact_whole", "aer.statevector", "qsim.statevector", "ddsim.statevector"]
|
||||
out.append({"id": "grover_n%d" % n, "what": "two Grover iterations on %d qubits: four Z gates under %d controls, the whole state" % (n, n - 1), "kind": "state", "n": n, "steps": corpus.grover_steps(n, (1 << n) // 3, 2), "engines": engines})
|
||||
for n, depth in ((1000, 30), (5000, 10)):
|
||||
steps = corpus.random_clifford(n, depth, 200 + n)
|
||||
engines = ["wai.stabilizer", "stim.tableau"] + (["aer.stabilizer"] if n <= 1000 else [])
|
||||
|
|
|
|||
|
|
@ -166,6 +166,20 @@ def results_section():
|
|||
mem = "%s (%s)" % (fmt_mib(row["peak_rss_mib"]), fmt_mib(row.get("baseline_rss_mib")))
|
||||
out.append("| %s | `%s` | %s | %s | %s | %s | %s → %s |" % (row["case"], row["engine"], row["threads"], row.get("ops") or "", sec, mem, row["load_before"][0], row["load_after"][0]))
|
||||
out.append("")
|
||||
for path in sorted(glob.glob(os.path.join(ROOT, "results", "before-after-*.json"))):
|
||||
p = json.load(open(path))
|
||||
out.append("### `%s`" % os.path.basename(path))
|
||||
out.append("")
|
||||
out.append("Host: %s. The same harness and circuits, with the engine under test built before and after a gate under many controls stood whole on its state vector, one build straight after the other: each figure is the engine's own call, the median of %d runs (the minimum in brackets), and ops are the operations it ran." % (host_line(p), p["repeat"]))
|
||||
out.append("")
|
||||
out.append("`wai.exact_whole`'s before rows ran a build with no such option, so they are standard gates, as their ops show. The last column is the one-minute load as each build's row began.")
|
||||
out.append("")
|
||||
out.append("| case | engine | threads | before: ops | before: seconds, median (min) | after: ops | after: seconds, median (min) | load average, before build → after build |")
|
||||
out.append("|---|---|---|---|---|---|---|---|")
|
||||
for row in p["rows"]:
|
||||
cell = lambda r: ("not run: %s" % r["not_run"][:90]) if r.get("not_run") else "%s (%s)" % (fmt_seconds(r["seconds_median"]), fmt_seconds(r["seconds_min"]))
|
||||
out.append("| %s | `%s` | %s | %s | %s | %s | %s | %s → %s |" % (row["case"], row["engine"], row["threads"], row["before"].get("ops") or "", cell(row["before"]), row["after"].get("ops") or "", cell(row["after"]), row["before"]["load_before"][0], row["after"]["load_before"][0]))
|
||||
out.append("")
|
||||
return "\n".join(out).rstrip() + "\n"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ Parameters:
|
|||
- `observables` (array): Pauli observables, as "Z0 Z1" or "0.5 X2".
|
||||
- `amplitudes` (array): Basis states whose amplitudes to return, as bit strings with the highest qubit first.
|
||||
- `noise` (object): Channel probabilities
|
||||
- `options` (object): Method options: max_bond, fusion, trajectories, threshold, memory_gb.
|
||||
- `options` (object): Method options: max_bond, fusion, controls, trajectories, threshold, memory_gb.
|
||||
- `compile` (object): Compile the program first (as compile_qasm does) and run the compiled circuit: with basis clifford_t it runs bit-exact, and the result's "compiled" states its distance from the program.
|
||||
- `target` (any): A device target: its name (list_targets) or a target document (wai.quantum.target/1). The program is then compiled to the target's gates, placed where it is most likely to succeed and simulated under the target's noise, the target's hash in the receipt; noise may not be given beside it.
|
||||
- `fabric` (any): Where the statevector method's state runs: auto (the default: on a member only where the committed measurements show it faster than this host's CPU for the job's size and fusion, otherwise the CPU; list_fabric states the rule and its numbers), cpu, accelerator (the first certified accelerator member that holds the state, the next taking over if it fails during the run), a member's id or key (list_fabric), or a list of members, a power of two of them, the state split by its top qubits into one partition on each; a pauli job's sum of terms runs on the member accelerator or its id or key names. Every member placed is certified bit-exact against the CPU, so the answers are the CPU's; the result's "fabric" names where the state ran, sealed by the receipt. A placement this host cannot honour is refused (J013).
|
||||
|
|
@ -79,7 +79,7 @@ Parameters:
|
|||
- `observables` (array): Pauli observables, as "Z0 Z1" or "0.5 X2".
|
||||
- `amplitudes` (array): Basis states whose amplitudes to return, as bit strings with the highest qubit first.
|
||||
- `noise` (object): Channel probabilities
|
||||
- `options` (object): Method options: max_bond, fusion, trajectories, threshold, memory_gb.
|
||||
- `options` (object): Method options: max_bond, fusion, controls, trajectories, threshold, memory_gb.
|
||||
- `compile` (object): Compile the program first (as compile_qasm does) and run the compiled circuit: with basis clifford_t it runs bit-exact, and the result's "compiled" states its distance from the program.
|
||||
- `target` (any): A device target: its name (list_targets) or a target document (wai.quantum.target/1). The program is then compiled to the target's gates, placed where it is most likely to succeed and simulated under the target's noise, the target's hash in the receipt; noise may not be given beside it.
|
||||
- `fabric` (any): Where the statevector method's state runs: auto (the default: on a member only where the committed measurements show it faster than this host's CPU for the job's size and fusion, otherwise the CPU; list_fabric states the rule and its numbers), cpu, accelerator (the first certified accelerator member that holds the state, the next taking over if it fails during the run), a member's id or key (list_fabric), or a list of members, a power of two of them, the state split by its top qubits into one partition on each; a pauli job's sum of terms runs on the member accelerator or its id or key names. Every member placed is certified bit-exact against the CPU, so the answers are the CPU's; the result's "fabric" names where the state ran, sealed by the receipt. A placement this host cannot honour is refused (J013).
|
||||
|
|
@ -431,7 +431,7 @@ Parameters:
|
|||
- `observables` (array): Pauli observables, as "Z0 Z1" or "0.5 X2".
|
||||
- `amplitudes` (array): Basis states whose amplitudes to return, as bit strings with the highest qubit first.
|
||||
- `noise` (object): Channel probabilities
|
||||
- `options` (object): Method options: max_bond, fusion, trajectories, threshold, memory_gb.
|
||||
- `options` (object): Method options: max_bond, fusion, controls, trajectories, threshold, memory_gb.
|
||||
- `compile` (object): Compile the program first (as compile_qasm does) and run the compiled circuit: with basis clifford_t it runs bit-exact, and the result's "compiled" states its distance from the program.
|
||||
- `target` (any, required): A device target: its name (list_targets) or a target document (wai.quantum.target/1). The program is then compiled to the target's gates, placed where it is most likely to succeed and simulated under the target's noise, the target's hash in the receipt; noise may not be given beside it.
|
||||
- `fabric` (any): Where the statevector method's state runs: auto (the default: on a member only where the committed measurements show it faster than this host's CPU for the job's size and fusion, otherwise the CPU; list_fabric states the rule and its numbers), cpu, accelerator (the first certified accelerator member that holds the state, the next taking over if it fails during the run), a member's id or key (list_fabric), or a list of members, a power of two of them, the state split by its top qubits into one partition on each; a pauli job's sum of terms runs on the member accelerator or its id or key names. Every member placed is certified bit-exact against the CPU, so the answers are the CPU's; the result's "fabric" names where the state ran, sealed by the receipt. A placement this host cannot honour is refused (J013).
|
||||
|
|
@ -632,7 +632,7 @@ Parameters:
|
|||
- `noise` (object): Optional noise, each a probability: depolarizing_1q, depolarizing_2q, amplitude_damping, dephasing (after each gate on its qubits), readout (each measured bit).
|
||||
- `method` (string, one of auto, exact, statevector, statevector_single, stabilizer, pauli, mps, tensor_network, cutrank, cutrank_single, stabilizer_rank, density_matrix, trajectories, pulse): Leave as auto unless you need a particular method; pulse runs a pulse program (OpenPulse calibrations on physical qubits, see pulse_schedule) under the pulse options, its error bounded.
|
||||
- `seed` (integer): Sampling seed (default 1); the same job and seed give the same counts.
|
||||
- `options` (object): Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), trajectories.
|
||||
- `options` (object): Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), controls (statevector and exact), trajectories.
|
||||
- `compile` (object): Compile the program first (as compile_qasm does) and run the compiled circuit: with basis clifford_t it runs bit-exact, and the result's `compiled` states its distance from the program.
|
||||
- `target` (any): A device target: a built-in target's name (list_targets) or a target document (wai.quantum.target/1). The program is then compiled to its gates, placed where it is most likely to succeed and simulated under its noise, its hash in the receipt; not with noise or amplitudes. The service reads the document given and fetches nothing.
|
||||
- `fabric` (any): Where the statevector method's state runs: auto (the default: on a member only where the committed measurements show it faster than the host's CPU for the job's size and fusion, otherwise the CPU), cpu, accelerator (any certified accelerator member), a member's id or key, or a list of members, a power of two of them, the state split by its top qubits into one partition on each; a pauli job's sum of terms runs on the member accelerator or its id or key names. Every member placed is certified bit-exact against the CPU, so the answers are the CPU's; the result's `fabric` names where the state ran, sealed by the receipt. A placement the service cannot honour is refused (J013): it places only on its CPU.
|
||||
|
|
@ -656,7 +656,7 @@ Parameters:
|
|||
- `noise` (object): Optional noise, each a probability: depolarizing_1q, depolarizing_2q, amplitude_damping, dephasing (after each gate on its qubits), readout (each measured bit).
|
||||
- `method` (string, one of auto, exact, statevector, statevector_single, stabilizer, pauli, mps, tensor_network, cutrank, cutrank_single, stabilizer_rank, density_matrix, trajectories, pulse): Leave as auto unless you need a particular method; pulse runs a pulse program (OpenPulse calibrations on physical qubits, see pulse_schedule) under the pulse options, its error bounded.
|
||||
- `seed` (integer): Sampling seed (default 1); the same job and seed give the same counts.
|
||||
- `options` (object): Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), trajectories.
|
||||
- `options` (object): Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), controls (statevector and exact), trajectories.
|
||||
- `compile` (object): Compile the program first (as compile_qasm does) and run the compiled circuit: with basis clifford_t it runs bit-exact, and the result's `compiled` states its distance from the program.
|
||||
- `target` (any): A device target: a built-in target's name (list_targets) or a target document (wai.quantum.target/1). The program is then compiled to its gates, placed where it is most likely to succeed and simulated under its noise, its hash in the receipt; not with noise or amplitudes. The service reads the document given and fetches nothing.
|
||||
- `fabric` (any): Where the statevector method's state runs: auto (the default: on a member only where the committed measurements show it faster than the host's CPU for the job's size and fusion, otherwise the CPU), cpu, accelerator (any certified accelerator member), a member's id or key, or a list of members, a power of two of them, the state split by its top qubits into one partition on each; a pauli job's sum of terms runs on the member accelerator or its id or key names. Every member placed is certified bit-exact against the CPU, so the answers are the CPU's; the result's `fabric` names where the state ran, sealed by the receipt. A placement the service cannot honour is refused (J013): it places only on its CPU.
|
||||
|
|
@ -681,7 +681,7 @@ Parameters:
|
|||
- `noise` (object): Optional noise, each a probability: depolarizing_1q, depolarizing_2q, amplitude_damping, dephasing (after each gate on its qubits), readout (each measured bit).
|
||||
- `method` (string, one of auto, exact, statevector, statevector_single, stabilizer, pauli, mps, tensor_network, cutrank, cutrank_single, stabilizer_rank, density_matrix, trajectories, pulse): Leave as auto unless you need a particular method; pulse runs a pulse program (OpenPulse calibrations on physical qubits, see pulse_schedule) under the pulse options, its error bounded.
|
||||
- `seed` (integer): Sampling seed (default 1); the same job and seed give the same counts.
|
||||
- `options` (object): Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), trajectories.
|
||||
- `options` (object): Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), controls (statevector and exact), trajectories.
|
||||
- `compile` (object): Compile the program first (as compile_qasm does) and run the compiled circuit: with basis clifford_t it runs bit-exact, and the result's `compiled` states its distance from the program.
|
||||
- `target` (any): A device target: a built-in target's name (list_targets) or a target document (wai.quantum.target/1). The program is then compiled to its gates, placed where it is most likely to succeed and simulated under its noise, its hash in the receipt; not with noise or amplitudes. The service reads the document given and fetches nothing.
|
||||
- `fabric` (any): Where the statevector method's state runs: auto (the default: on a member only where the committed measurements show it faster than the host's CPU for the job's size and fusion, otherwise the CPU), cpu, accelerator (any certified accelerator member), a member's id or key, or a list of members, a power of two of them, the state split by its top qubits into one partition on each; a pauli job's sum of terms runs on the member accelerator or its id or key names. Every member placed is certified bit-exact against the CPU, so the answers are the CPU's; the result's `fabric` names where the state ran, sealed by the receipt. A placement the service cannot honour is refused (J013): it places only on its CPU.
|
||||
|
|
@ -828,7 +828,7 @@ Parameters:
|
|||
- `noise` (object): Optional noise, each a probability: depolarizing_1q, depolarizing_2q, amplitude_damping, dephasing (after each gate on its qubits), readout (each measured bit).
|
||||
- `params` (object): A value for each of the program's parameters (input float theta;, input angle phi;), as {"theta": 0.3}: bound exactly before the program is read; a parameter left without one is refused (J050). In a sweep, the parameters it fixes.
|
||||
- `seed` (integer): Sampling seed (default 1); the same job and seed give the same counts.
|
||||
- `options` (object): Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), trajectories.
|
||||
- `options` (object): Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), controls (statevector and exact), trajectories.
|
||||
- `compile` (object): Compile the program first (as compile_qasm does) and run the compiled circuit: with basis clifford_t it runs bit-exact, and the result's `compiled` states its distance from the program.
|
||||
- `target` (any, required): A device target: a built-in target's name (list_targets) or a target document (wai.quantum.target/1). The program is then compiled to its gates, placed where it is most likely to succeed and simulated under its noise, its hash in the receipt; not with noise or amplitudes. The service reads the document given and fetches nothing.
|
||||
- `fabric` (any): Where the statevector method's state runs: auto (the default: on a member only where the committed measurements show it faster than the host's CPU for the job's size and fusion, otherwise the CPU), cpu, accelerator (any certified accelerator member), a member's id or key, or a list of members, a power of two of them, the state split by its top qubits into one partition on each; a pauli job's sum of terms runs on the member accelerator or its id or key names. Every member placed is certified bit-exact against the CPU, so the answers are the CPU's; the result's `fabric` names where the state ran, sealed by the receipt. A placement the service cannot honour is refused (J013): it places only on its CPU.
|
||||
|
|
@ -1068,7 +1068,7 @@ Each construct below is supported (read as the language defines it), lowered (re
|
|||
|
||||
| Construct | Status | Codes |
|
||||
|---|---|---|
|
||||
| `ctrl @`, `ctrl(n) @`, `negctrl @`, `negctrl(n) @` on any gate, user gates included | lowered exactly (a negative control is a control between two `x`); at most 16 added controls | Q201, Q207, Q208 |
|
||||
| `ctrl @`, `ctrl(n) @`, `negctrl @`, `negctrl(n) @` on any gate, user gates included | lowered exactly (a negative control is a control between two `x`); at most 16 added controls in standard gates. A job on `statevector` holds a gate whose standard gates would double as one operation, under as many as 255, and lowers it only where a method needs standard gates (`qasm::read_whole`); `exact` does so only when asked (`"options": {"controls": "whole"}`), since its bits are those of the gates it applies. Those gates are `x`, `y`, `z` and `h` under three or more controls and `s`, `sdg`, `t`, `tdg`, `p`, `rx`, `ry`, `rz`, `u`, `sx` and `sxdg` under two or more, each lowered to the method's gates under its controls (`sx` as `h s h`, `ry` as `sdg h rz h s`), and not `p(pi)`, which is `z`. A job with an `intent` is held to 16: an intent is checked in standard gates, while a verdict with none reads the program as it ran | Q201, Q207, Q208 |
|
||||
| `inv @` on any gate | lowered exactly: each gate's inverse, in reverse order | Q202 |
|
||||
| `pow(k) @`, `k` a whole number | lowered exactly: angles scaled or the gate repeated; negative `k` repeats the inverse | Q203 |
|
||||
| `pow(r) @`, `r` fractional | lowered where an exact principal root exists (`pow(1/2) @ x` is `sx`; rotations by less than 2π; phases; controlled forms); refused otherwise (`u`, `cu`, user gates) | Q204, Q205 |
|
||||
|
|
@ -1235,9 +1235,9 @@ Every code:
|
|||
| `Q205` | refused | `pow(r) @` with a fractional `r` where no exact gate exists here: a user gate, `u`, `cu`, or a rotation by 2π or more. |
|
||||
| `Q206` | lowered | `gphase(θ)`: a global phase, which no measurement sees, dropped; under controls it is a phase on the controls. |
|
||||
| `Q207` | refused | A control modifier that adds no control: `ctrl(0) @`. |
|
||||
| `Q208` | refused | Modifiers that add more controls than the reader lowers (16). |
|
||||
| `Q208` | refused | Modifiers that add more controls than the reader lowers (16 written in standard gates, 255 on a gate a method holds whole). |
|
||||
| `Q209` | refused | Gate definitions or subroutine calls nested deeper than 32. |
|
||||
| `Q210` | refused | A program that expands to more gates than the reader holds (2 000 000). |
|
||||
| `Q210` | refused | A program that expands to more gates than the reader holds (2 000 000), or that holds gates whole on more qubits than that in all. |
|
||||
| `Q211` | refused | A modifier on something that is not a gate. |
|
||||
| `Q212` | refused | A statement a gate's body may not hold: a gate holds gate calls, `gphase`, `barrier`, loops and conditions known on reading. |
|
||||
| `Q300` | lowered | An `if`, `while` or `switch` whose condition is known on reading: decided then. |
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Method options: max_bond, fusion, trajectories, threshold, memory_gb."
|
||||
"description": "Method options: max_bond, fusion, controls, trajectories, threshold, memory_gb."
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -727,7 +727,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -1442,7 +1452,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -1471,6 +1487,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -4492,7 +4573,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -5205,7 +5296,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -5234,6 +5331,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -8851,7 +9013,7 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Method options: max_bond, fusion, trajectories, threshold, memory_gb."
|
||||
"description": "Method options: max_bond, fusion, controls, trajectories, threshold, memory_gb."
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -9295,7 +9457,8 @@
|
|||
},
|
||||
"work": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The method's own unit: an upper bound on what the run takes where the method's cost is known beforehand, and with `metered` what the meter holds it to."
|
||||
},
|
||||
"qubits": {
|
||||
"type": "integer",
|
||||
|
|
@ -9350,6 +9513,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"metered": {
|
||||
"type": "boolean",
|
||||
"description": "True when a job naming method mps would pass one job's work at the bond it asks for: it runs under a meter of the bonds it reaches, may take far less than `work`, and stops with J011 if it passes `work`."
|
||||
},
|
||||
"verdict": {
|
||||
"title": "wai.quantum.verdict/1",
|
||||
"description": "How far the engine checked an answer about a program, as one level of an ordered ladder (wai-quantum://docs/verdicts): refused, parsed, executed, evidence, proof. A level says how far the engine checked, not that the answer is good: holds says whether the intents held (true, false, or null when none was decided). A job's result carries one under verdict when the job gives an intent or asks for one, and the receipt seals it; both MCP servers state one beside every answer about a program.",
|
||||
|
|
@ -10181,7 +10348,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates in standard gates, at most 2^53 - 1."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When a gate under many controls would double in standard gates: the operations a job on the state vector runs, each such gate one."
|
||||
},
|
||||
"bits": {
|
||||
"type": "integer",
|
||||
|
|
@ -30280,7 +30453,7 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Method options: max_bond, fusion, trajectories, threshold, memory_gb."
|
||||
"description": "Method options: max_bond, fusion, controls, trajectories, threshold, memory_gb."
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -30891,7 +31064,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -31606,7 +31789,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -31635,6 +31824,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -34656,7 +34910,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -35369,7 +35633,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -35398,6 +35668,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -50778,7 +51113,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -51493,7 +51838,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -51522,6 +51873,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -54543,7 +54959,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -55256,7 +55682,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -55285,6 +55717,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), trajectories."
|
||||
"description": "Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), controls (statevector and exact), trajectories."
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -743,7 +743,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -1458,7 +1468,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -1487,6 +1503,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -4508,7 +4589,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -5221,7 +5312,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -5250,6 +5347,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -10183,7 +10345,7 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), trajectories."
|
||||
"description": "Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), controls (statevector and exact), trajectories."
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -10839,7 +11001,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -11554,7 +11726,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -11583,6 +11761,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -14604,7 +14847,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -15317,7 +15570,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -15346,6 +15605,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -20279,7 +20603,7 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), trajectories."
|
||||
"description": "Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), controls (statevector and exact), trajectories."
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -21955,7 +22279,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -22670,7 +23004,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -22699,6 +23039,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -25720,7 +26125,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -26433,7 +26848,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -26462,6 +26883,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -44042,7 +44528,7 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), trajectories."
|
||||
"description": "Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), controls (statevector and exact), trajectories."
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -44687,7 +45173,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -45402,7 +45898,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -45431,6 +45933,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -48452,7 +49019,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
@ -49165,7 +49742,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"type": "object",
|
||||
|
|
@ -49194,6 +49777,71 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": [
|
||||
"turns_over_2_33",
|
||||
"radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"turns_over_2_33": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The phase exactly, in 2^33rds of a turn."
|
||||
},
|
||||
"radians": {
|
||||
"type": "number",
|
||||
"description": "The same in radians, in (-pi, pi]."
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": [
|
||||
"angles",
|
||||
"largest_radians",
|
||||
"note"
|
||||
],
|
||||
"properties": {
|
||||
"angles": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"largest_radians": {
|
||||
"type": "number"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
@ -52401,7 +53049,17 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": [
|
||||
"whole",
|
||||
"standard"
|
||||
],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"type": "object",
|
||||
|
|
|
|||
|
|
@ -11,9 +11,13 @@
|
|||
//! macOS the harness reads it when the process ends. A job names its `kind`:
|
||||
//!
|
||||
//! - `state`: the program's whole state, written to `state_out` with qubit 0 as
|
||||
//! the lowest bit of the index. `method` `exact` writes `i64` pairs at scale
|
||||
//! `2^30` (with the state's hash and its op-log, which an independent reader
|
||||
//! reruns); `statevector` writes `f64` pairs, at fusion width `fusion`.
|
||||
//! the lowest bit of the index, each gate under many controls held as a job
|
||||
//! holds it: whole on `statevector`, in standard gates on `exact` unless
|
||||
//! `controls` is `whole` (`standard` writes standard gates for both). `method`
|
||||
//! `exact` writes `i64` pairs at scale `2^30` (with the state's hash, its
|
||||
//! op-log, which an independent reader reruns, and the global phase the
|
||||
//! job's result states); `statevector` writes `f64` pairs, at fusion width
|
||||
//! `fusion`.
|
||||
//! - `job`: `request` through [`wai_quantum::engine::job::plan_and_run`], the
|
||||
//! engine's whole path: its plan, its run and its answers, unsealed.
|
||||
//! - `stabilizers`: the program on the tableau, and `⟨g⟩` of each Pauli string
|
||||
|
|
@ -53,12 +57,22 @@ fn text<'a>(j: &'a Value, k: &str) -> Result<&'a str, String> {
|
|||
}
|
||||
|
||||
fn state(j: &Value) -> Result<Value, String> {
|
||||
let program = qasm::read(text(j, "qasm")?).map_err(|d| d.to_string())?.program;
|
||||
let method = text(j, "method")?;
|
||||
// As a job runs it: a gate under many controls held whole on the state
|
||||
// vector, and on the bit-exact method only when asked ("controls": "whole");
|
||||
// "controls": "standard" writes standard gates for both.
|
||||
let whole = match j.get("controls").and_then(Value::as_str) {
|
||||
Some("standard") => false,
|
||||
Some("whole") => true,
|
||||
_ => method == "statevector",
|
||||
};
|
||||
let src = text(j, "qasm")?;
|
||||
let program = if whole { qasm::read_whole(src) } else { qasm::read(src) }.map_err(|d| d.to_string())?.program;
|
||||
let n = program.n_qubits;
|
||||
let out = j.get("state_out").and_then(Value::as_str);
|
||||
match text(j, "method")? {
|
||||
match method {
|
||||
"exact" => {
|
||||
let c = program.dyadic()?;
|
||||
let (c, report) = program.dyadic_with_report()?;
|
||||
let t = Instant::now();
|
||||
let sv = c.simulate().map_err(|e| e.to_string())?;
|
||||
let seconds = t.elapsed().as_secs_f64();
|
||||
|
|
@ -66,14 +80,14 @@ fn state(j: &Value) -> Result<Value, String> {
|
|||
write_pairs(path, sv.amps.iter().map(|a| (a.re, a.im)), i64::to_le_bytes)?;
|
||||
}
|
||||
let ops: Vec<Value> = c.ops.iter().map(|g| json!([g.base as u8, g.controls, g.target, g.param])).collect();
|
||||
Ok(json!({ "n": n, "seconds": seconds, "state_hash": hex(&sv.statevector_hash()), "ops": if j.get("ops").and_then(Value::as_bool).unwrap_or(true) { Value::Array(ops) } else { Value::Null }, "n_ops": c.ops.len() }))
|
||||
Ok(json!({ "n": n, "seconds": seconds, "state_hash": hex(&sv.statevector_hash()), "ops": if j.get("ops").and_then(Value::as_bool).unwrap_or(true) { Value::Array(ops) } else { Value::Null }, "n_ops": c.ops.len(), "global_phase": report.global_phase_radians() }))
|
||||
}
|
||||
"statevector" => {
|
||||
let gates = program.matrices();
|
||||
let gates = program.sv_ops();
|
||||
let width = j.get("fusion").and_then(Value::as_u64).unwrap_or(3) as usize;
|
||||
let mut sv = StateVector::zero(n).map_err(|e| format!("{e:?}"))?;
|
||||
let t = Instant::now();
|
||||
sv.run(&gates, width).map_err(|e| format!("{e:?}"))?;
|
||||
sv.run_ops(&gates, width).map_err(|e| format!("{e:?}"))?;
|
||||
let seconds = t.elapsed().as_secs_f64();
|
||||
if let Some(path) = out {
|
||||
// Qubit 0 is the state vector's highest bit; the file has it lowest.
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ fn job_schema() -> Value {
|
|||
"observables": { "type": "array", "items": { "type": "string" }, "description": "Pauli observables, as \"Z0 Z1\" or \"0.5 X2\"." },
|
||||
"amplitudes": { "type": "array", "items": { "type": "string" }, "description": "Basis states whose amplitudes to return, as bit strings with the highest qubit first." },
|
||||
"noise": { "type": "object", "description": "Channel probabilities", "properties": catalog::NOISE_CHANNELS.iter().map(|k| (k.to_string(), json!({ "type": "number", "minimum": 0, "maximum": 1 }))).collect::<serde_json::Map<_, _>>(), "additionalProperties": false },
|
||||
"options": { "type": "object", "description": "Method options: max_bond, fusion, trajectories, threshold, memory_gb." },
|
||||
"options": { "type": "object", "description": "Method options: max_bond, fusion, controls, trajectories, threshold, memory_gb." },
|
||||
"compile": compile_options_schema("Compile the program first (as compile_qasm does) and run the compiled circuit: with basis clifford_t it runs bit-exact, and the result's \"compiled\" states its distance from the program."),
|
||||
"target": { "description": "A device target: its name (list_targets) or a target document (wai.quantum.target/1). The program is then compiled to the target's gates, placed where it is most likely to succeed and simulated under the target's noise, the target's hash in the receipt; noise may not be given beside it." },
|
||||
"fabric": fabric_schema(),
|
||||
|
|
@ -1030,6 +1030,18 @@ mod tests {
|
|||
SigningKey::from_bytes(&[5; 32])
|
||||
}
|
||||
|
||||
/// An agent reaches what the docs say a job on the state vector holds: a
|
||||
/// gate under 20 controls runs, its verdict beside it.
|
||||
#[test]
|
||||
fn a_gate_past_16_controls_runs_over_mcp() {
|
||||
let qs: Vec<String> = (0..21).map(|i| format!("q[{i}]")).collect();
|
||||
let args = json!({ "qasm": format!("OPENQASM 3.0;\nqubit[21] q;\nx q;\nctrl(20) @ z {};\n", qs.join(", ")), "method": "statevector", "shots": 0, "amplitudes": ["1".repeat(21)] });
|
||||
let r = handle(&json!({ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "run_quantum_circuit", "arguments": args } }), 1, &key(), &mut Client::default()).unwrap()["result"].clone();
|
||||
assert_eq!(r["isError"], false, "{r}");
|
||||
assert_eq!(r["structuredContent"]["result"]["operations"], 22, "{r}");
|
||||
assert_eq!(r["structuredContent"]["result"]["verdict"]["level"], "executed", "{r}");
|
||||
}
|
||||
|
||||
/// A run takes its program in another format as the job service's run
|
||||
/// tools take it: the same `source`, read to its qasm first, and the same
|
||||
/// refusal, the reader's code before the job layer's.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ A job is JSON:
|
|||
| `observables` | Pauli strings such as `"Z0 Z1"`, or `{"pauli": "X3 Y4", "coeff": 0.5}` |
|
||||
| `amplitudes` | bit strings, one character per qubit, qubit `n − 1` first |
|
||||
| `noise` | `{"depolarizing_1q", "depolarizing_2q", "amplitude_damping", "dephasing", "readout"}`, each a probability |
|
||||
| `options` | per method: `threshold`, `max_weight` and `ladder` (pauli), `max_bond` and `cutoff` (mps), `fusion` (statevector), `trajectories`, `memory_gb` |
|
||||
| `options` | per method: `threshold`, `max_weight` and `ladder` (pauli), `max_bond` and `cutoff` (mps), `fusion` (statevector), `controls` (statevector and exact: `"standard"` or `"whole"`, a gate under many controls in standard gates or as one), `trajectories`, `memory_gb` |
|
||||
| `compile` | `{"epsilon", "level", "basis"}`: compile the program first (as `POST /v1/compile` does) and run the compiled circuit. With `basis` `clifford_t` (the default) it runs on the bit-exact method, and the result's `compiled` states its distance from the program |
|
||||
| `intent` | what the program must do: `{"distribution": {...}}`, `{"equivalent": "<a reference program>"}`, `{"prepares": "<a reference program>"}`, or a list. The result's `verdict` states the level the engine checked to (refused, parsed, executed, evidence, proof) and whether the intents hold, sealed by the receipt ([the verdict page](../wai-quantum/docs/verdicts.md)). A reference's check is held to the equivalence bound below |
|
||||
| `verdict` | `true`: the verdict with no intent |
|
||||
|
|
@ -81,7 +81,7 @@ curl -s https://wai.transaction.science/quantum-jobs/v1/run -H 'content-type: ap
|
|||
| `statevector_single` | any circuit with gates on at most four qubits, up to 33 qubits, where the host has a GPU | counts, expectations, amplitudes | single |
|
||||
| `stabilizer` | Clifford circuits (rotations by whole quarter turns), up to 32768 qubits, optionally with depolarizing, dephasing and readout noise | counts, expectations | exact; with noise, an estimate |
|
||||
| `pauli` | any circuit, up to 1024 qubits | expectations | bounded |
|
||||
| `mps` | any circuit, up to 4096 qubits, at bond dimension up to 256; best when entanglement stays low | counts, expectations, amplitudes | exact when nothing is truncated, otherwise heuristic |
|
||||
| `mps` | any circuit, up to 4096 qubits, at bond dimension up to 256; best when entanglement stays low. Named, a job past one job's work at the bond it asks for runs under a meter of the bonds it reaches (`metered` in its plan, whose `work` is then what the meter allows) and stops with `J011` if it passes it | counts, expectations, amplitudes | exact when nothing is truncated, otherwise heuristic |
|
||||
| `tensor_network` | any circuit | amplitudes | exact |
|
||||
| `cutrank` | a Clifford circuit, then one layer of one-qubit gates at any angles, up to 4096 qubits, as wide as the state's cut-rank allows | counts, amplitudes | exact; single when only a single-precision table fits |
|
||||
| `cutrank_single` | as `cutrank`, with the table on the host's GPU, where it has one; only when asked for | counts, amplitudes | single |
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ pub fn openapi(server: &str) -> Value {
|
|||
"dephasing": { "type": "number", "minimum": 0, "maximum": 1 },
|
||||
"readout": { "type": "number", "minimum": 0, "maximum": 1 }
|
||||
}, "description": "Each a probability, applied after every gate on the qubits it touched (readout: each measured bit)." },
|
||||
"options": { "type": "object", "description": "Per method: threshold, max_weight, ladder, max_terms (pauli); max_bond, cutoff (mps); fusion (statevector); trajectories; memory_gb." },
|
||||
"options": { "type": "object", "description": "Per method: threshold, max_weight, ladder, max_terms (pauli); max_bond, cutoff (mps); fusion, controls (statevector); controls (exact); trajectories; memory_gb." },
|
||||
"compile": { "$ref": "#/components/schemas/CompileOptions", "description": "Compile the program first (as POST /v1/compile does) and run the compiled circuit; with basis clifford_t it runs on the bit-exact method, and the result's `compiled` states its distance from the program and the certificate's hash." },
|
||||
"target": {
|
||||
"oneOf": [
|
||||
|
|
|
|||
|
|
@ -844,12 +844,15 @@ impl Service {
|
|||
q.pop_front();
|
||||
}
|
||||
};
|
||||
// A metered job was planned at what its meter allows, not at what it
|
||||
// took: its time says nothing of the method's rate.
|
||||
match &job.work {
|
||||
Work::One if job.plan.metered => {}
|
||||
Work::One => push(&job.plan.method, job.plan.work, r, &r["energy"]),
|
||||
// A set's items are timed one by one; the set's energy covers them all.
|
||||
Work::Set { plans, .. } => {
|
||||
for (it, p) in r["items"].as_array().into_iter().flatten().zip(plans) {
|
||||
if it["status"] == "done" {
|
||||
if it["status"] == "done" && !p.metered {
|
||||
push(&p.method, p.work, &it["result"], &Value::Null);
|
||||
}
|
||||
}
|
||||
|
|
@ -1363,7 +1366,7 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
fn plan(memory_bytes: u64) -> Plan {
|
||||
Plan { method: "statevector".into(), why: "test".into(), memory_bytes, work: 1, qubits: 3, target: None }
|
||||
Plan { method: "statevector".into(), why: "test".into(), memory_bytes, work: 1, qubits: 3, target: None, metered: false }
|
||||
}
|
||||
|
||||
fn config() -> Config {
|
||||
|
|
@ -1795,10 +1798,24 @@ mod tests {
|
|||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// A job that ran under a meter was planned at what its meter allows, not
|
||||
/// at what it took: its time says nothing of the method's rate.
|
||||
#[test]
|
||||
fn a_metered_job_does_not_set_the_rate() {
|
||||
let s = Service::new(config());
|
||||
let p = Plan { method: "mps".into(), why: "".into(), memory_bytes: 1, work: 1_000_000_000, qubits: 30, target: None, metered: true };
|
||||
for _ in 0..3 {
|
||||
let job = Job { id: job_id(), client: "x".into(), request: json!({}), plan: p.clone(), work: Work::One, status: Status::Done, submitted_ms: 0, started_ms: None, finished_ms: Some(1), result: Some(json!({ "elapsed_ms": 500, "energy": { "joules_micro": 5_000_000, "energy_uncertainty": { "window_us": 500_000 } } })), error: None, peak_rss: 0, digests: Digests::default(), key: None, interruptions: Vec::new(), child: None };
|
||||
s.time(&job);
|
||||
}
|
||||
let (t, _) = s.estimate(&p);
|
||||
assert!(t["basis"].as_str().unwrap().contains("nominal"), "{t}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_estimate_says_its_basis_nominal_then_measured_here() {
|
||||
let s = Service::new(config());
|
||||
let p = Plan { method: "statevector".into(), why: "".into(), memory_bytes: 1, work: 2_000_000_000, qubits: 20, target: None };
|
||||
let p = Plan { method: "statevector".into(), why: "".into(), memory_bytes: 1, work: 2_000_000_000, qubits: 20, target: None, metered: false };
|
||||
let (t, e) = s.estimate(&p);
|
||||
assert!((t["seconds"].as_f64().unwrap() - 2.0).abs() < 1e-9 && t["basis"].as_str().unwrap().contains("nominal"), "{t}");
|
||||
assert!(e.get("unestimated").is_some(), "{e}");
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ fn tool_list() -> Value {
|
|||
} },
|
||||
"method": { "type": "string", "enum": ["auto", "exact", "statevector", "statevector_single", "stabilizer", "pauli", "mps", "tensor_network", "cutrank", "cutrank_single", "stabilizer_rank", "density_matrix", "trajectories", "pulse"], "description": "Leave as auto unless you need a particular method; pulse runs a pulse program (OpenPulse calibrations on physical qubits, see pulse_schedule) under the pulse options, its error bounded." },
|
||||
"seed": { "type": "integer", "description": "Sampling seed (default 1); the same job and seed give the same counts." },
|
||||
"options": { "type": "object", "description": "Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), trajectories." },
|
||||
"options": { "type": "object", "description": "Advanced, per method: max_bond (mps), threshold and max_terms (pauli), fusion (statevector), controls (statevector and exact), trajectories." },
|
||||
"compile": { "type": "object", "description": "Compile the program first (as compile_qasm does) and run the compiled circuit: with basis clifford_t it runs bit-exact, and the result's `compiled` states its distance from the program.", "properties": compile_props() },
|
||||
"target": { "description": "A device target: a built-in target's name (list_targets) or a target document (wai.quantum.target/1). The program is then compiled to its gates, placed where it is most likely to succeed and simulated under its noise, its hash in the receipt; not with noise or amplitudes. The service reads the document given and fetches nothing." },
|
||||
"fabric": { "description": "Where the statevector method's state runs: auto (the default: on a member only where the committed measurements show it faster than the host's CPU for the job's size and fusion, otherwise the CPU), cpu, accelerator (any certified accelerator member), a member's id or key, or a list of members, a power of two of them, the state split by its top qubits into one partition on each; a pauli job's sum of terms runs on the member accelerator or its id or key names. Every member placed is certified bit-exact against the CPU, so the answers are the CPU's; the result's `fabric` names where the state ran, sealed by the receipt. A placement the service cannot honour is refused (J013): it places only on its CPU.", "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "array", "minItems": 2, "maxItems": 64, "items": { "type": "string", "minLength": 1 } }] },
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -62,7 +62,7 @@ browser engine. A test holds this page to the code's table.
|
|||
| `J008` | refused | The mitigation the job asked for does not read or cannot be done: no noise model, no observables (no shots, for readout), a program that measures in the middle, more qubits or work than it takes, a device target beside it, or options that do not read (the message says which). |
|
||||
| `J009` | refused | An error-correction request that does not read or cannot be done: an unknown code or decoder, a decoder the code does not admit, a syndrome or an error that does not fit the code, circuit text or a model that does not read, or sizes past the limits (the message says which). |
|
||||
| `J010` | refused | A method that does not exist: the methods list names them. |
|
||||
| `J011` | refused | The method named cannot take this job: a gate it does not hold, more qubits, memory or work than it takes here, or an answer it does not give (noise it cannot take is `J061`); the message says which, and `auto` picks a method that can. |
|
||||
| `J011` | refused | The method named cannot take this job: a gate it does not hold, more qubits, memory or work than it takes here, or an answer it does not give (noise it cannot take is `J061`); the message says which, and `auto` picks a method that can. A job on `mps` runs under a meter of the bonds it reaches, so this can also come from a job that has started: it stopped where its work passed what a job may take. |
|
||||
| `J012` | refused | No method takes this job: each method's reason is listed, so the job can be changed to suit one. |
|
||||
| `J013` | refused | A placement on the fabric (`fabric`) this host cannot honour: no member by that name, no certified accelerator member here, a state or a partition larger than a member holds, a partition count that is not a power of two, or a method or program the fabric's members do not run (the message says which, and what is here). |
|
||||
| `J020` | refused | An intent that does not read: not one of `distribution`, `equivalent` or `prepares`, a distribution whose outcomes are not the program's bits or whose probabilities do not sum to 1, or a reference program that does not read. |
|
||||
|
|
|
|||
|
|
@ -87,9 +87,9 @@ engine. A test holds this page to the code's table.
|
|||
| `Q205` | refused | `pow(r) @` with a fractional `r` where no exact gate exists here: a user gate, `u`, `cu`, or a rotation by 2π or more. |
|
||||
| `Q206` | lowered | `gphase(θ)`: a global phase, which no measurement sees, dropped; under controls it is a phase on the controls. |
|
||||
| `Q207` | refused | A control modifier that adds no control: `ctrl(0) @`. |
|
||||
| `Q208` | refused | Modifiers that add more controls than the reader lowers (16). |
|
||||
| `Q208` | refused | Modifiers that add more controls than the reader lowers (16 written in standard gates, 255 on a gate a method holds whole). |
|
||||
| `Q209` | refused | Gate definitions or subroutine calls nested deeper than 32. |
|
||||
| `Q210` | refused | A program that expands to more gates than the reader holds (2 000 000). |
|
||||
| `Q210` | refused | A program that expands to more gates than the reader holds (2 000 000), or that holds gates whole on more qubits than that in all. |
|
||||
| `Q211` | refused | A modifier on something that is not a gate. |
|
||||
| `Q212` | refused | A statement a gate's body may not hold: a gate holds gate calls, `gphase`, `barrier`, loops and conditions known on reading. |
|
||||
| `Q300` | lowered | An `if`, `while` or `switch` whose condition is known on reading: decided then. |
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ and in the browser build.
|
|||
|
||||
| Construct | Status | Codes |
|
||||
|---|---|---|
|
||||
| `ctrl @`, `ctrl(n) @`, `negctrl @`, `negctrl(n) @` on any gate, user gates included | lowered exactly (a negative control is a control between two `x`); at most 16 added controls | Q201, Q207, Q208 |
|
||||
| `ctrl @`, `ctrl(n) @`, `negctrl @`, `negctrl(n) @` on any gate, user gates included | lowered exactly (a negative control is a control between two `x`); at most 16 added controls in standard gates. A job on `statevector` holds a gate whose standard gates would double as one operation, under as many as 255, and lowers it only where a method needs standard gates (`qasm::read_whole`); `exact` does so only when asked (`"options": {"controls": "whole"}`), since its bits are those of the gates it applies. Those gates are `x`, `y`, `z` and `h` under three or more controls and `s`, `sdg`, `t`, `tdg`, `p`, `rx`, `ry`, `rz`, `u`, `sx` and `sxdg` under two or more, each lowered to the method's gates under its controls (`sx` as `h s h`, `ry` as `sdg h rz h s`), and not `p(pi)`, which is `z`. A job with an `intent` is held to 16: an intent is checked in standard gates, while a verdict with none reads the program as it ran | Q201, Q207, Q208 |
|
||||
| `inv @` on any gate | lowered exactly: each gate's inverse, in reverse order | Q202 |
|
||||
| `pow(k) @`, `k` a whole number | lowered exactly: angles scaled or the gate repeated; negative `k` repeats the inverse | Q203 |
|
||||
| `pow(r) @`, `r` fractional | lowered where an exact principal root exists (`pow(1/2) @ x` is `sx`; rotations by less than 2π; phases; controlled forms); refused otherwise (`u`, `cu`, user gates) | Q204, Q205 |
|
||||
|
|
|
|||
|
|
@ -120,10 +120,18 @@ their errors.
|
|||
error model text, the subset the crate's circuits use and more: tags on
|
||||
instructions (`DEPOLARIZE1[leak](0.01) 0`), Pauli-target observables
|
||||
(`OBSERVABLE_INCLUDE(0) X0 Z3`), detector coordinates, `REPEAT` blocks,
|
||||
`SHIFT_COORDS`, and in models `^`-separated decompositions, `detector` and
|
||||
`logical_observable` declarations, `repeat` and `shift_detectors`. A
|
||||
circuit's model written back keeps its tags and coordinates. What the reader
|
||||
does not take it refuses with the line, never skipping it. The
|
||||
`SHIFT_COORDS`, the axis-cycling gates `C_XYZ` and `C_ZYX`, correlated errors
|
||||
(`E(p) X1 Y2 Z3`, also `CORRELATED_ERROR`) and one-qubit Pauli channels
|
||||
(`PAULI_CHANNEL_1(px, py, pz)`), and in models `^`-separated decompositions,
|
||||
`detector` and `logical_observable` declarations, `repeat` and
|
||||
`shift_detectors`. A correlated error is a flip on a qubit of its own, handed
|
||||
to its targets, so a circuit with one holds one qubit more than its text
|
||||
names. A Pauli channel becomes the independent X, Y and Z flips with its law,
|
||||
a rate above 1/2 among them where the law needs one; a channel with no such
|
||||
flips (`PAULI_CHANNEL_1(0.1, 0.1, 0)`) is refused, since a model of
|
||||
independent mechanisms cannot state it. A circuit's model written back keeps
|
||||
its tags and coordinates. What the reader does not take it refuses with the
|
||||
line, never skipping it. The
|
||||
[conformance vectors](../../quantum-conformance/qec-text/) hold each form,
|
||||
the model each circuit gives, and each refusal.
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ impl Method for BasisTable {
|
|||
return Err("basis_table answers counts and amplitudes".into());
|
||||
}
|
||||
let n = r.program.n_qubits;
|
||||
Ok(Plan { method: "basis_table".into(), why: format!("a permutation of basis states on {n} qubits, followed bit by bit"), memory_bytes: 1 << 20, work: r.program.ops.len() as u64, qubits: n, target: None })
|
||||
Ok(Plan { method: "basis_table".into(), why: format!("a permutation of basis states on {n} qubits, followed bit by bit"), memory_bytes: 1 << 20, work: r.program.ops.len() as u64, qubits: n, target: None, metered: false })
|
||||
}
|
||||
fn run(&self, job: &Job<'_>, _: &Plan, out: &mut Value) -> Result<(), String> {
|
||||
let r = job.request()?;
|
||||
|
|
|
|||
|
|
@ -137,7 +137,14 @@
|
|||
},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts), and memory_gb."
|
||||
"description": "Options of the method that runs the job, as the methods list names them (max_bond, cutoff, fusion, trajectories, threshold, max_weight, ladder, max_terms, path_restarts, controls), and memory_gb.",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"controls": {
|
||||
"enum": ["whole", "standard"],
|
||||
"description": "A gate whose standard gates would double (x, y, z or h under three or more controls; s, sdg, t, tdg, p, rx, ry, rz, u, sx or sxdg under two or more) runs as one on statevector and in standard gates on exact. \"standard\" runs standard gates on statevector too, which reproduces a result made that way bit for bit. \"whole\" holds the gate whole on exact too, which gives other bits than its default, and is refused (J003) on a job that runs standard gates: another method named, a compile, noise, mitigation, a device target or a named place on the fabric. Any other value is refused."
|
||||
}
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"$ref": "compile-result.v1.json#/$defs/options"
|
||||
|
|
|
|||
|
|
@ -49,7 +49,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates, in standard gates (at most 2^53 - 1), however the method ran them."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When the method held a gate under many controls as one (statevector, and exact asked for it): the operations it ran."
|
||||
},
|
||||
"accuracy": {
|
||||
"$ref": "#/$defs/accuracy"
|
||||
|
|
@ -765,6 +771,36 @@
|
|||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"global_phase": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: method statevector's state of the program that ran is e^(i radians) times the amplitudes given, since this method's lowering drops the phase of rz, rx, ry, rxx, ryy, rzz and u. Absent when that is 1. The language's own state can differ from both: its U is e^(i theta / 2) times the matrix both methods take as u, the reader drops an uncontrolled gphase (Q206), and a compiled job's program is the compile's, right up to a global phase.",
|
||||
"required": ["turns_over_2_33", "radians", "note"],
|
||||
"properties": {
|
||||
"turns_over_2_33": { "type": "integer", "minimum": 0, "description": "The phase exactly, in 2^33rds of a turn." },
|
||||
"radians": { "type": "number", "description": "The same in radians, in (-pi, pi]." },
|
||||
"note": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"below_table": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering that are not zero and ran as no phase, within the reader's tolerance (1e-14 of a turn) of a whole turn. `angles` counts those gates: one angle written may be several, and another count in another form of the same program.",
|
||||
"required": ["angles", "largest_radians", "note"],
|
||||
"properties": {
|
||||
"angles": { "type": "integer", "minimum": 1 },
|
||||
"largest_radians": { "type": "number" },
|
||||
"note": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"moved_angles": {
|
||||
"type": "object",
|
||||
"description": "The bit-exact method: phase gates of its lowering within the reader's tolerance (1e-14 of a turn) of a dyadic angle, by more than a double's rounding of it, that ran as it. `angles` counts those gates.",
|
||||
"required": ["angles", "largest_radians", "note"],
|
||||
"properties": {
|
||||
"angles": { "type": "integer", "minimum": 1 },
|
||||
"largest_radians": { "type": "number" },
|
||||
"note": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"fusion": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@
|
|||
},
|
||||
"work": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The method's own unit: an upper bound on what the run takes where the method's cost is known beforehand, and with `metered` what the meter holds it to."
|
||||
},
|
||||
"qubits": {
|
||||
"type": "integer",
|
||||
|
|
@ -32,6 +33,10 @@
|
|||
},
|
||||
"target": {
|
||||
"$ref": "job-result.v1.json#/$defs/target_placement"
|
||||
},
|
||||
"metered": {
|
||||
"type": "boolean",
|
||||
"description": "True when a job naming method mps would pass one job's work at the bond it asks for: it runs under a meter of the bonds it reaches, may take far less than `work`, and stops with J011 if it passes `work`."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,13 @@
|
|||
},
|
||||
"gates": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
"minimum": 0,
|
||||
"description": "The program's gates in standard gates, at most 2^53 - 1."
|
||||
},
|
||||
"operations": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "When a gate under many controls would double in standard gates: the operations a job on the state vector runs, each such gate one."
|
||||
},
|
||||
"bits": {
|
||||
"type": "integer",
|
||||
|
|
|
|||
|
|
@ -109,6 +109,10 @@ pub struct Plan {
|
|||
/// target (`engine::targets::job`).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub target: Option<Value>,
|
||||
/// The run is held to `work` by a meter of what it reaches, and may take
|
||||
/// far less: `work` says nothing of how fast the method goes.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub metered: bool,
|
||||
}
|
||||
|
||||
/// The work a method does in a second, nominally, for a time estimate on a
|
||||
|
|
@ -161,8 +165,8 @@ pub fn describe(job_bytes: u64) -> Value {
|
|||
/// hashed into its identity).
|
||||
pub(crate) fn method_text(method: &str) -> Value {
|
||||
match method {
|
||||
"exact" => json!({ "accuracy": "bit-exact", "circuits": "every angle a dyadic multiple of 2π (2πm/2^k, k ≤ 32)", "answers": ["counts", "expectations", "amplitudes", "state_hash"] }),
|
||||
"statevector" => json!({ "accuracy": "exact", "circuits": "any", "answers": ["counts", "expectations", "amplitudes"] }),
|
||||
"exact" => json!({ "accuracy": "bit-exact", "circuits": "every angle a dyadic multiple of 2π (2πm/2^k, k ≤ 32)", "answers": ["counts", "expectations", "amplitudes", "state_hash"], "options": { "controls": "whole: a gate whose standard gates would double runs as one, under as many as 255 controls, with other bits than the default (standard)" } }),
|
||||
"statevector" => json!({ "accuracy": "exact", "circuits": "any", "answers": ["counts", "expectations", "amplitudes"], "options": { "controls": "standard: a gate under many controls runs in standard gates, reproducing a result made that way bit for bit (default whole: it runs as one, under as many as 255 controls)" } }),
|
||||
"statevector_single" => json!({ "accuracy": "single", "circuits": "any, gates on at most four qubits", "answers": ["counts", "expectations", "amplitudes"], "options": { "fusion": "the widest fused gate (default 3, at most 3)" } }),
|
||||
"stabilizer" => json!({ "accuracy": "exact; with Pauli noise, counts are noisy samples and expectations estimates", "circuits": "Clifford (rotations by whole quarter turns, controlled phases by whole half turns), optionally with depolarizing, dephasing and readout noise", "answers": ["counts", "expectations"], "options": { "trajectories": "with noise: the shots expectation values are estimated from (default: the shots, at least 10000)" } }),
|
||||
"pauli" => json!({ "accuracy": "bounded", "circuits": "any", "answers": ["expectations"], "options": { "threshold": "coefficients below it are dropped (default 1e-5); the bound covers what was dropped", "max_weight": "Pauli strings heavier than this are dropped", "ladder": "also report the value at up to three thresholds a decade apart above it, to show it settling (default true)", "max_terms": "the most Pauli terms to hold (default: what the job's memory holds); past it the threshold rises, and the bound covers what drops" } }),
|
||||
|
|
@ -291,7 +295,9 @@ pub fn statevector_max_qubits(job_bytes: u64) -> u32 {
|
|||
/// as `(qubit, 'X'|'Y'|'Z')`.
|
||||
pub type Observable = (String, f64, Vec<(u32, char)>);
|
||||
|
||||
/// A job as submitted, read and checked.
|
||||
/// A job as submitted, read and checked. Its `program` is in standard gates,
|
||||
/// except for a built-in method that runs a gate under many controls as one
|
||||
/// ([`runs_whole`]).
|
||||
pub struct Request {
|
||||
pub program: Program,
|
||||
/// The reader's warnings and notes on the program, each with its code.
|
||||
|
|
@ -318,6 +324,86 @@ pub struct Request {
|
|||
pub mitigate: Option<Value>,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
/// The request with its program in standard gates ([`Program::lowered`]);
|
||||
/// refused where the standard form cannot hold it.
|
||||
pub(crate) fn lowered(&self) -> Result<Request, String> {
|
||||
Ok(Request {
|
||||
program: self.program.lowered().map_err(String::from)?,
|
||||
diagnostics: self.diagnostics.clone(),
|
||||
method: self.method.clone(),
|
||||
shots: self.shots,
|
||||
seed: self.seed,
|
||||
observables: self.observables.clone(),
|
||||
amplitudes: self.amplitudes.clone(),
|
||||
noise: self.noise,
|
||||
options: self.options.clone(),
|
||||
compiled: self.compiled.clone(),
|
||||
compile_passes: self.compile_passes.clone(),
|
||||
repaired: self.repaired.clone(),
|
||||
mitigate: self.mitigate.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a built-in method runs a gate under many controls as one: the
|
||||
/// state vector, and the bit-exact method when the job asks for it with
|
||||
/// `"options": {"controls": "whole"}`. Every other takes the program in
|
||||
/// standard gates. The bit-exact method takes them by default because its bits
|
||||
/// are the rounding of each gate it applies: the standard gates give the bits
|
||||
/// it always gave, and the gate held whole gives others.
|
||||
pub(crate) fn runs_whole(method: &str, v: &Value) -> bool {
|
||||
match method {
|
||||
"statevector" => true,
|
||||
"exact" => whole_asked(v),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the job asks for a gate under many controls held whole on the
|
||||
/// bit-exact method too: `"options": {"controls": "whole"}`.
|
||||
pub(crate) fn whole_asked(v: &Value) -> bool {
|
||||
v.pointer("/options/controls").and_then(Value::as_str) == Some("whole")
|
||||
}
|
||||
|
||||
/// Whether this job may hold a gate under many controls as one op
|
||||
/// ([`qasm::read_whole`]). Not with a compile, which writes standard gates; not
|
||||
/// with noise or mitigation, whose channels follow standard gates (a job with
|
||||
/// noise reaches no method that runs whole in any case); not on a device
|
||||
/// target or a provider, compiled to its gates; not when the job names a
|
||||
/// place on the fabric, where every member runs the standard gates and the
|
||||
/// state is the CPU's, bit for bit; and not when it asks for
|
||||
/// `"options": {"controls": "standard"}`, which reproduces a state-vector
|
||||
/// result made in standard gates, bit for bit.
|
||||
pub(crate) fn may_run_whole(v: &Value) -> bool {
|
||||
let absent = |k: &str| v.get(k).is_none_or(Value::is_null);
|
||||
absent("compile")
|
||||
&& absent("noise")
|
||||
&& absent("mitigate")
|
||||
&& absent("target")
|
||||
&& absent("provider")
|
||||
&& matches!(super::fabric::asked(v), Ok(super::fabric::Ask::Auto))
|
||||
&& v.pointer("/options/controls").and_then(Value::as_str) != Some("standard")
|
||||
}
|
||||
|
||||
/// `options.controls` read: `"standard"`, or `"whole"` on a job that can hold a
|
||||
/// gate whole, on a method that does ([`runs_whole`]; `auto` takes it where it
|
||||
/// lands on one). A value left unread would give another result in silence.
|
||||
fn read_controls(v: &Value) -> Result<(), String> {
|
||||
let Some(c) = v.pointer("/options/controls").filter(|c| !c.is_null()) else {
|
||||
return Ok(());
|
||||
};
|
||||
match c.as_str() {
|
||||
Some("standard") => Ok(()),
|
||||
Some("whole") if !may_run_whole(v) => Err(tag(refusal::BAD_FIELD, "options.controls \"whole\": this job runs standard gates, as one with a compile, noise, mitigation, a device target or a named place on the fabric does; leave it out")),
|
||||
Some("whole") => match v.get("method").and_then(Value::as_str).unwrap_or("auto") {
|
||||
"auto" | "exact" | "statevector" => Ok(()),
|
||||
m => Err(tag(refusal::BAD_FIELD, format!("options.controls \"whole\": {m} runs standard gates; exact and statevector hold a gate whole"))),
|
||||
},
|
||||
_ => Err(tag(refusal::BAD_FIELD, format!("options.controls is \"whole\" or \"standard\", not {c}"))),
|
||||
}
|
||||
}
|
||||
|
||||
fn observable(v: &Value, n: u32) -> Result<Observable, String> {
|
||||
let (text, coeff) = match v {
|
||||
Value::String(s) => (s.clone(), 1.0),
|
||||
|
|
@ -474,7 +560,8 @@ pub fn bind_params(src: &str, params: Option<&Value>) -> Result<String, String>
|
|||
qasm::bind(src, &values).map_err(|e| tag(refusal::PARAMS, e))
|
||||
}
|
||||
|
||||
/// Reads a job's JSON. A refusal ends with its code ([`super::refusal`]).
|
||||
/// Reads a job's JSON, its program in standard gates. A refusal ends with its
|
||||
/// code ([`super::refusal`]).
|
||||
pub fn read(v: &Value) -> Result<Request, String> {
|
||||
read_in(Registry::builtins(), v).map(|(r, _)| r)
|
||||
}
|
||||
|
|
@ -483,11 +570,18 @@ pub fn read(v: &Value) -> Result<Request, String> {
|
|||
/// channel its noise names with a value other than zero, by its place in
|
||||
/// the registry with its values.
|
||||
pub(crate) fn read_in(reg: &Registry, v: &Value) -> Result<(Request, Uses), String> {
|
||||
read_form(reg, v, false)
|
||||
}
|
||||
|
||||
/// [`read_in`], with each gate under many controls held as one op when `whole`
|
||||
/// (and the job may hold one, [`may_run_whole`]).
|
||||
pub(crate) fn read_form(reg: &Registry, v: &Value, whole: bool) -> Result<(Request, Uses), String> {
|
||||
// A set runs as a set, item by item; read as one job it would run one
|
||||
// program in place of all of them.
|
||||
if is_set(v) {
|
||||
return Err(tag(refusal::BAD_SET, "this request is a set (\"programs\" or \"sweep\"): it runs as a set, one item at a time, where sets are taken (the job service, or wai-quantum run and submit)"));
|
||||
}
|
||||
read_controls(v)?;
|
||||
// A provider job runs on the person's own device, through a host that
|
||||
// holds their credentials (engine::provider); the engine's methods never
|
||||
// stand in for it.
|
||||
|
|
@ -513,8 +607,14 @@ pub(crate) fn read_in(reg: &Registry, v: &Value) -> Result<(Request, Uses), Stri
|
|||
(qasm, Some(said))
|
||||
}
|
||||
};
|
||||
let reading = qasm::read(&src)?;
|
||||
let reading = if whole && may_run_whole(v) { qasm::read_whole(&src)? } else { qasm::read(&src)? };
|
||||
let (program, diagnostics) = (reading.program, reading.diagnostics);
|
||||
// An intent is checked in standard gates, whichever way the job runs: one
|
||||
// they cannot hold is refused as the reader refuses it, on its line. A
|
||||
// verdict with no intent reads the program as it ran.
|
||||
if program.has_whole() && v.get("intent").is_some_and(|i| !i.is_null()) {
|
||||
qasm::read(&src)?;
|
||||
}
|
||||
// "intent": what the program must do, checked with its result
|
||||
// (super::verdict); refused here when it does not read.
|
||||
if let Some(i) = v.get("intent").filter(|i| !i.is_null()) {
|
||||
|
|
@ -623,15 +723,20 @@ pub fn mps_bond(r: &Request) -> usize {
|
|||
r.options.get("max_bond").and_then(Value::as_u64).map(|b| b as usize).unwrap_or(MPS_DEFAULT_BOND).clamp(1, MPS_MAX_BOND)
|
||||
}
|
||||
|
||||
/// An `mps` job's work: its two-site updates (each two-qubit gate and the
|
||||
/// swaps that route it) at `(2χ)³` each, and its shots at `n·χ²` each.
|
||||
/// An `mps` job's work: the bound [`WorkBound`](crate::quantum_mpsim::WorkBound)
|
||||
/// puts on the chain's run of its gates at bond `chi` (each two-site update,
|
||||
/// with the swaps that route a gate, at `(2χ)³`, each move of the
|
||||
/// orthogonality center at `4χ³`, each one-qubit gate at `4χ²`), and its
|
||||
/// shots at `n·χ²` each.
|
||||
pub fn mps_work(r: &Request, chi: u64) -> u64 {
|
||||
let updates: u64 = r.program.ops.iter().map(|o| match o.qubits.as_slice() {
|
||||
[a, b] => 2 * (a.abs_diff(*b) as u64).saturating_sub(1) + 1,
|
||||
[a, b, c] => 15 * (1 + 2 * (a.abs_diff(*b).max(b.abs_diff(*c)).max(a.abs_diff(*c)) as u64)),
|
||||
_ => 0,
|
||||
}).sum();
|
||||
updates.saturating_mul((2 * chi).pow(3)).saturating_add(r.shots.saturating_mul(r.program.n_qubits as u64 * chi * chi * 2))
|
||||
let mut bound = crate::quantum_mpsim::WorkBound::new(chi);
|
||||
r.program.local_steps(|_, qubits, _| bound.gate(qubits));
|
||||
bound.work().saturating_add(mps_shots_work(r, chi))
|
||||
}
|
||||
|
||||
/// An `mps` job's shots, at bond `chi`, in [`mps_work`]'s unit.
|
||||
pub(crate) fn mps_shots_work(r: &Request, chi: u64) -> u64 {
|
||||
r.shots.saturating_mul(r.program.n_qubits as u64 * chi * chi * 2)
|
||||
}
|
||||
|
||||
/// A `cutrank` job's state: the program's Clifford part as a graph form with
|
||||
|
|
@ -769,7 +874,7 @@ fn try_dynamic(method: &str, r: &Request, clifford: &Result<Vec<crate::quantum_t
|
|||
if !r.amplitudes.is_empty() {
|
||||
return Err("a program that measures in the middle leaves no single state to give amplitudes of; ask for counts or expectation values".into());
|
||||
}
|
||||
let plan = |memory_bytes: u64, work: u64, why: String| Plan { method: method.into(), why, memory_bytes, work, qubits: n, target: None };
|
||||
let plan = |memory_bytes: u64, work: u64, why: String| Plan { method: method.into(), why, memory_bytes, work, qubits: n, target: None, metered: false };
|
||||
match method {
|
||||
"statevector" => {
|
||||
// At least two states (a branch's copy beside the state it left),
|
||||
|
|
@ -881,7 +986,7 @@ pub fn stabilizer_rank_work(c: &crate::quantum_stabrank::CliffordT, amplitude_te
|
|||
pub(crate) fn try_method(method: &str, r: &Request, dyadic: &Result<crate::quantum::Circuit, String>, clifford: &Result<Vec<crate::quantum_tableau::Clifford>, String>, cap: u64) -> Result<Plan, String> {
|
||||
let n = r.program.n_qubits;
|
||||
let gates = r.program.ops.len() as u64;
|
||||
let plan = |memory_bytes: u64, work: u64, why: String| Plan { method: method.into(), why, memory_bytes, work, qubits: n, target: None };
|
||||
let plan = |memory_bytes: u64, work: u64, why: String| Plan { method: method.into(), why, memory_bytes, work, qubits: n, target: None, metered: false };
|
||||
let counts = r.shots > 0;
|
||||
let wants_amplitudes = !r.amplitudes.is_empty();
|
||||
let wants_expectations = !r.observables.is_empty();
|
||||
|
|
@ -897,7 +1002,13 @@ pub(crate) fn try_method(method: &str, r: &Request, dyadic: &Result<crate::quant
|
|||
return Err(format!("exact holds at most {EXACT_MAX_QUBITS} qubits"));
|
||||
}
|
||||
let bytes = (48u64 << n) + 128 * MIB;
|
||||
Ok(plan(bytes, gates << n, format!("bit-exact state vector of {n} qubits")))
|
||||
let mut why = format!("bit-exact state vector of {n} qubits");
|
||||
if let Ok((_, report)) = r.program.dyadic_with_report()
|
||||
&& report.below_table > 0
|
||||
{
|
||||
why = format!("{why}; {} angle(s) below its table, the largest {:e} rad, run as no phase and the result says so", report.below_table, report.largest_below_table);
|
||||
}
|
||||
Ok(plan(bytes, gates << n, why))
|
||||
}
|
||||
"statevector" => {
|
||||
let bytes = statevector_bytes(n);
|
||||
|
|
@ -973,11 +1084,21 @@ pub(crate) fn try_method(method: &str, r: &Request, dyadic: &Result<crate::quant
|
|||
}
|
||||
let chi = mps_bond(r) as u64;
|
||||
let work = mps_work(r, chi);
|
||||
if work > MPS_MAX_WORK {
|
||||
return Err(format!("this circuit at bond dimension {chi} is more work than one job may take; ask for a smaller max_bond or fewer shots"));
|
||||
// At the bond the job asks for, this bounds the work from above. A
|
||||
// job that names the method runs under a meter of the bond its
|
||||
// circuit reaches (run_mps); it is refused here only if even the
|
||||
// smallest bond that entangles is too much. `auto` keeps to what
|
||||
// the bound admits, and picks no job it may have to stop.
|
||||
let named = r.method == "mps";
|
||||
if work > MPS_MAX_WORK && (!named || mps_work(r, 2) > MPS_MAX_WORK) {
|
||||
return Err(format!("this circuit at bond dimension {chi} is more work than one job may take ({work} at that bond, at most {MPS_MAX_WORK}); ask for a smaller max_bond or fewer shots{}", if named { "" } else { ", or name method mps, which meters the bond the circuit reaches" }));
|
||||
}
|
||||
let memory = (n as u64) * chi * chi * 64 + (2 * chi) * (2 * chi) * 96 + 256 * MIB;
|
||||
Ok(plan(memory, work, format!("matrix product state of {n} qubits at bond dimension {chi}")))
|
||||
// Past what a job may take at that bond, the meter holds the run to
|
||||
// what a job may take: all that can be said of its work beforehand.
|
||||
let metered = work > MPS_MAX_WORK;
|
||||
let said = if metered { format!("; its work at that bond would pass one job's ({work}, at most {MPS_MAX_WORK}), so it runs metered on the bond it reaches and stops [{}] if that passes it", refusal::METHOD_REFUSES) } else { String::new() };
|
||||
Ok(Plan { metered, ..plan(memory, work.min(MPS_MAX_WORK), format!("matrix product state of {n} qubits at bond dimension {chi}{said}")) })
|
||||
}
|
||||
"cutrank" => {
|
||||
use crate::quantum_cutrank::Precision;
|
||||
|
|
@ -1153,8 +1274,16 @@ pub(crate) fn plan_in(reg: &Registry, v: &Value, job_bytes: u64) -> Result<Plan,
|
|||
if v.get("target").is_some_and(|t| !t.is_null()) {
|
||||
return Err("this build has no device targets (the engine's quantum_receipt feature)".into());
|
||||
}
|
||||
let (r, uses) = read_in(reg, v)?;
|
||||
let job = Job::new(reg, v, job_bytes, Some(&r), &uses);
|
||||
// A method named that needs standard gates reads them, so what they cannot
|
||||
// hold is refused as the reader refuses it, on its line.
|
||||
let whole = v.get("method").and_then(Value::as_str).filter(|m| *m != "auto").is_none_or(|m| runs_whole(m, v));
|
||||
let (r, uses) = read_form(reg, v, whole)?;
|
||||
// Written in standard gates only if a method that needs them is asked.
|
||||
let standard = std::cell::OnceCell::new();
|
||||
let mut job = Job::new(reg, v, job_bytes, Some(&r), &uses);
|
||||
if r.program.has_whole() {
|
||||
job = job.with_standard(&standard);
|
||||
}
|
||||
let mut p = choose_placed(&job)?;
|
||||
super::fabric::admit(v, &r, &mut p)?;
|
||||
with_intent(reg, v, &r, p, job_bytes)
|
||||
|
|
@ -1165,7 +1294,7 @@ pub(crate) fn plan_in(reg: &Registry, v: &Value, job_bytes: u64) -> Result<Plan,
|
|||
/// the fabric's members run, `statevector`.
|
||||
fn choose_placed(job: &Job<'_>) -> Result<Plan, String> {
|
||||
use super::fabric::Ask;
|
||||
let r = job.request()?;
|
||||
let r = job.as_read()?;
|
||||
let ask = super::fabric::asked(job.json())?;
|
||||
if r.method != "auto" || matches!(ask, Ask::Auto | Ask::Cpu) {
|
||||
return choose(job);
|
||||
|
|
@ -1189,7 +1318,7 @@ fn with_intent(reg: &Registry, v: &Value, r: &Request, mut p: Plan, job_bytes: u
|
|||
/// every channel the job names held to what the method takes (each refusal
|
||||
/// of a channel `J061`).
|
||||
pub(crate) fn plan_one(m: &dyn Method, job: &Job<'_>) -> Result<Plan, String> {
|
||||
let r = job.request()?;
|
||||
let r = job.as_read()?;
|
||||
if r.noise.is_some() && !m.models_noise() {
|
||||
return Err(tag(refusal::CHANNEL_REFUSED, format!("{} does not model noise", m.name())));
|
||||
}
|
||||
|
|
@ -1206,7 +1335,7 @@ pub(crate) fn plan_one(m: &dyn Method, job: &Job<'_>) -> Result<Plan, String> {
|
|||
/// The method for the job: the one it names, or the first `auto` finds, in
|
||||
/// the registry's order for what the job asks ([`Question`]).
|
||||
fn choose(job: &Job<'_>) -> Result<Plan, String> {
|
||||
let r = job.request()?;
|
||||
let r = job.as_read()?;
|
||||
let reg = job.registry();
|
||||
if r.method != "auto" {
|
||||
let Some(m) = reg.method(&r.method) else {
|
||||
|
|
@ -1229,6 +1358,9 @@ fn choose(job: &Job<'_>) -> Result<Plan, String> {
|
|||
Err(tag(refusal::NO_METHOD, format!("no method takes this job — {}", refusals.join("; "))))
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "quantum_receipt"))]
|
||||
mod whole_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -1243,6 +1375,26 @@ mod tests {
|
|||
|
||||
const CAP: u64 = 72 * GIB;
|
||||
|
||||
/// The work an `mps` job is planned at bounds the meter of its run: the
|
||||
/// moves of the orthogonality center (gates at the two ends of a chain
|
||||
/// whose middle holds the bond, in turn), and the gates a three-qubit gate
|
||||
/// is written in, among it.
|
||||
#[test]
|
||||
fn the_mps_plan_bounds_its_run() {
|
||||
use crate::quantum_mpsim::{Chain, MpsConfig};
|
||||
let fill = "qubit[12] q; h q; for uint l in [0:5] { rx(0.3) q; for uint k in [0:5] { cx q[2 * k], q[2 * k + 1]; } ry(0.4) q; for uint k in [0:4] { cx q[2 * k + 1], q[2 * k + 2]; } }";
|
||||
let ends = format!("{fill} for uint i in [0:29] {{ cx q[0], q[1]; rx(0.3) q[0]; cx q[10], q[11]; ry(0.2) q[11]; }}");
|
||||
let wide = "qubit[12] q; h q; ccx q[0], q[6], q[11]; cx q[11], q[0]; rx(0.3) q; cswap q[2], q[9], q[5]; ccz q[10], q[1], q[4]; cx q[0], q[11]; ry(0.2) q; cx q[3], q[8]; cx q[11], q[1]; t q[7];";
|
||||
for src in [ends.as_str(), wide] {
|
||||
let program = crate::qasm::read(src).unwrap().program;
|
||||
for chi in [2u64, 8, 64] {
|
||||
let p = plan(&job(src, json!({ "method": "mps", "shots": 1, "options": { "max_bond": chi } })), CAP).unwrap();
|
||||
let c = Chain::run(12, &program.local_matrices(), MpsConfig { max_bond: chi as usize, cutoff: 1e-12 }).unwrap();
|
||||
assert!(c.work() > 0 && c.work() <= p.work, "bond {chi}: the run's {} over the plan's {}", c.work(), p.work);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_picks_the_strongest_method_the_circuit_admits() {
|
||||
let bell = "qubit[2] q; h q[0]; cx q[0], q[1];";
|
||||
|
|
|
|||
667
wai/wai-quantum/src/engine/catalog/whole_tests.rs
Normal file
667
wai/wai-quantum/src/engine/catalog/whole_tests.rs
Normal file
|
|
@ -0,0 +1,667 @@
|
|||
//! A gate under many controls through a job: which methods hold it whole, what
|
||||
//! stays as it was, and what a result says.
|
||||
|
||||
use super::*;
|
||||
use crate::engine::job::plan_and_run;
|
||||
use crate::engine::{compile, estimate, formats};
|
||||
|
||||
const CAP: u64 = 16 << 30;
|
||||
|
||||
fn hx(b: &[u8]) -> String {
|
||||
blake3::hash(b).as_bytes()[..8].iter().map(|x| format!("{x:02x}")).collect()
|
||||
}
|
||||
|
||||
/// Each program with what the engine gave of it before a gate stood whole, on
|
||||
/// arm64 and x86_64 alike (each row taken from that commit on both): its
|
||||
/// standard gates and their digest, the bit-exact state hash, the compile's
|
||||
/// output, the QIR and jeff exports and the resource estimate's counts.
|
||||
#[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", "5483575a012810bb", "747ea02fde8aeaca"),
|
||||
("c3x", "qubit[5] q;\nh q;\nctrl(3) @ x q[0], q[1], q[2], q[3];\n", 36, "7a786cc271f2501b", "8a504be7fc0299e8", "19be6f5bfc085d84", "0c304a3bc21ab20d", "c35804da5d616eb1", "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", "0c039f9aa8aaf730", "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", "412b56a798ee15ad", "744da0841af0f928"),
|
||||
("c2p", "qubit[4] q;\nh q;\nctrl(2) @ p(pi / 8) q[0], q[1], q[2];\n", 17, "b076ca7327b88b22", "13346662e1739d87", "18407afca6242403", "82518b8435656332", "1ad0afc42fc8a458", "0da1476c3de0dffa"),
|
||||
("c2rz", "qubit[4] q;\nh q;\nctrl(2) @ rz(pi / 4) q[0], q[1], q[2];\n", 18, "ade98300c7a91bea", "ca049c0c8dd637ed", "a4f6414c00826c92", "0f6065ef438dadba", "74d61b41579349ab", "d2f07d8d123fa85a"),
|
||||
("c2rx", "qubit[4] q;\nh q;\nctrl(2) @ rx(pi / 2) q[0], q[1], q[2];\n", 20, "6ae6ae2309534fbb", "3eab347bbdb8b27c", "5a664df2c0c8b771", "4dc76727674b23fa", "c607efaafd1b104c", "d992c105de67f83c"),
|
||||
("c2ry", "qubit[4] q;\nh q;\nctrl(2) @ ry(pi / 2) q[0], q[1], q[2];\n", 22, "27369425f1ce33f2", "94f7f25bde25caef", "947dd07eb6275558", "27aeedd8797a64c3", "92bc2218593c67d2", "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", "c4d07e54180ae52d", "94c720252c873ede"),
|
||||
("c2sx", "qubit[4] q;\nh q;\nctrl(2) @ sx q[0], q[1], q[2];\n", 21, "3011642a37dab1c8", "378d6c6c3635da01", "e65547fe84b0d8cf", "8a53373f168fcddd", "77db3f01554b29e4", "c3e8535f252e0170"),
|
||||
("neg", "qubit[4] q;\nh q;\nnegctrl(2) @ ctrl @ s q[0], q[1], q[2], q[3];\n", 37, "64475caf52138761", "77667386517f1f09", "1c755ad6249197a1", "e46c16636e80eebd", "901334ecfa216508", "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", "5d1f6d595cdfbe0d", "b27c6c0bb9082d58"),
|
||||
("c2swap", "qubit[4] q;\nh q;\nctrl(2) @ swap q[0], q[1], q[2], q[3];\n", 37, "90d28cfbf2beef6c", "7982dfa1bd563330", "76fee435a36b077d", "f1554f2143fd5485", "0ce2bc73b4fa722f", "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", "9486d505cc21b5a0", "d992c105de67f83c"),
|
||||
("c2gphase", "qubit[3] q;\nh q;\nctrl(2) @ gphase(pi / 4) q[0], q[1];\n", 4, "613a1f7f53c2c634", "0e065cabc1032cc6", "eb75b70b655bedca", "87c33623c70c4586", "e5c2502f696c5154", "709224f43ac6c30e"),
|
||||
("invc3t", "qubit[4] q;\nh q;\ninv @ ctrl(3) @ t q[0], q[1], q[2], q[3];\n", 33, "6d8735fa38b73015", "8ac349a04bd30ca8", "389140fbf3231c9f", "cfc33faa8c2f6e40", "ab64f2e385bb59da", "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", "dcba716313b6ece1", "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", "b6b70437c04ac558", "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", "3b78a28be6428788", "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", "281d8093937d891e", "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", "ac5f279656d62dc7", "d76ade781f528b10"),
|
||||
("cp", "qubit[3] q;\nh q;\nctrl @ p(pi / 8) q[0], q[1];\n", 4, "48ca258d13245d56", "cddd783aad2071d2", "e062f8754960a2d9", "ed65712144049bea", "6db848b83c6a454c", "0e28272539b04b2a"),
|
||||
("cswap", "qubit[3] q;\nh q;\nctrl @ swap q[0], q[1], q[2];\n", 4, "2a5e8a545c769f54", "70fe25ec22008f86", "27282765b5abf030", "e85d174f9e4e97da", "87a240aab7d4fc0e", "694084d5478b13ed"),
|
||||
("plain", "qubit[3] q;\nh q;\ncx q[0], q[1];\nrz(pi / 4) q[2];\n", 5, "8103a71b8f08fb58", "92dbd1cf0f4ce0e1", "275c3c9040292ce0", "2bd714b375f08ed0", "368eeef29b7aa1f2", "68eebfa353685fb7"),
|
||||
];
|
||||
|
||||
/// What needs standard gates gives what it gave, byte for byte: the reader's
|
||||
/// `read`, a compile, the QIR and jeff exports, the resource estimate, and the
|
||||
/// bit-exact method, by default and asked for standard gates, whose hash is the
|
||||
/// one made before gates stood whole.
|
||||
#[test]
|
||||
fn what_takes_standard_gates_gives_what_it_gave() {
|
||||
for (name, src, ops, digest, state, compiled, qir, jeff, counts) in BEFORE {
|
||||
let p = qasm::read(src).unwrap().program;
|
||||
assert_eq!(p.ops.len(), *ops, "{name}");
|
||||
let mut t = String::new();
|
||||
for o in &p.ops {
|
||||
t += &format!("{:?} {:?} {:?}\n", o.kind, o.qubits, o.params.iter().map(|x| x.to_bits()).collect::<Vec<_>>());
|
||||
}
|
||||
assert_eq!(hx(t.as_bytes()), *digest, "{name}: the standard gates");
|
||||
for options in [json!({}), json!({ "controls": "standard" })] {
|
||||
let job = json!({ "qasm": src, "method": "exact", "shots": 0, "amplitudes": ["0".repeat(p.n_qubits as usize)], "options": options });
|
||||
let (_, out) = plan_and_run(&job, CAP).unwrap();
|
||||
assert_eq!(&out["state_hash"].as_str().unwrap()[..16], *state, "{name}: the bit-exact hash, options {options}");
|
||||
assert_eq!(out["gates"], json!(*ops), "{name}: {out}");
|
||||
assert!(out.get("operations").is_none(), "{name}: {out}");
|
||||
}
|
||||
// The receipt's provenance counts the same gates on the state vector, held whole or not.
|
||||
let (_, out) = plan_and_run(&json!({ "qasm": src, "method": "statevector", "shots": 0, "amplitudes": ["0".repeat(p.n_qubits as usize)] }), CAP).unwrap();
|
||||
assert_eq!(out["gates"], json!(*ops), "{name}: {out}");
|
||||
assert_eq!(hx(compile::compile(&p, &compile::Options::default()).unwrap().qasm.as_bytes()), *compiled, "{name}: the compile");
|
||||
assert_eq!(hx(&formats::write(formats::Format::Qir, &p, None).unwrap().bytes), *qir, "{name}: QIR");
|
||||
assert_eq!(hx(&formats::write(formats::Format::Jeff, &p, None).unwrap().bytes), *jeff, "{name}: jeff");
|
||||
assert_eq!(hx(estimate::call(&json!({ "qasm": src })).unwrap()["counts"].to_string().as_bytes()), *counts, "{name}: the estimate");
|
||||
// Given a program holding gates whole, each writes its standard gates.
|
||||
let w = qasm::read_whole(src).unwrap().program;
|
||||
assert_eq!(hx(compile::compile(&w, &compile::Options::default()).unwrap().qasm.as_bytes()), *compiled, "{name}: the compile, from the whole form");
|
||||
assert_eq!(hx(&formats::write(formats::Format::Qir, &w, None).unwrap().bytes), *qir, "{name}: QIR, from the whole form");
|
||||
assert_eq!(estimate::count::lower_program(&w).unwrap(), estimate::count::lower_program(&p).unwrap(), "{name}: the estimate's circuit, from the whole form");
|
||||
}
|
||||
}
|
||||
|
||||
fn wide(n: usize) -> String {
|
||||
(0..n).map(|i| format!("q[{i}]")).collect::<Vec<_>>().join(", ")
|
||||
}
|
||||
|
||||
/// `iterations` Grover iterations on `n` qubits, every qubit marked by `x`.
|
||||
fn grover(n: usize, iterations: usize) -> String {
|
||||
let qs = wide(n);
|
||||
let mut s = format!("qubit[{n}] q;\nh q;\n");
|
||||
for _ in 0..iterations {
|
||||
s += &format!("ctrl({}) @ z {qs};\nh q;\nx q;\nctrl({}) @ z {qs};\nx q;\nh q;\n", n - 1, n - 1);
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// Two Grover iterations on 14 qubits run as 130 operations, not 131,186
|
||||
/// gates, on the state vector, and on the bit-exact method when the job asks
|
||||
/// for it; `gates` still counts the standard gates. The marked state's
|
||||
/// amplitude is the closed form's, within each method's bound.
|
||||
#[test]
|
||||
fn a_job_on_the_state_vectors_holds_the_gate_whole() {
|
||||
let (n, k) = (14usize, 2usize);
|
||||
let src = grover(n, k);
|
||||
let marked = "1".repeat(n);
|
||||
let theta = (2f64.powf(-(n as f64) / 2.0)).asin();
|
||||
let want = ((2 * k + 1) as f64 * theta).sin();
|
||||
for (method, options, bound) in [("exact", json!({ "controls": "whole" }), 1e-5), ("statevector", json!({}), 1e-12)] {
|
||||
let job = json!({ "qasm": src, "method": method, "shots": 0, "amplitudes": [marked], "options": options });
|
||||
let (plan, out) = plan_and_run(&job, CAP).unwrap();
|
||||
assert_eq!((plan.method.as_str(), out["operations"].as_u64(), out["gates"].as_u64()), (method, Some(130), Some(131_186)), "{out}");
|
||||
let a = &out["amplitudes"][0];
|
||||
assert!((a["re"].as_f64().unwrap().hypot(a["im"].as_f64().unwrap()) - want.abs()).abs() < bound, "{method}: {a}, want {want}");
|
||||
}
|
||||
// The bit-exact method's bits are the same on every host: this test holds
|
||||
// the whole form's there.
|
||||
let (_, out) = plan_and_run(&json!({ "qasm": src, "method": "exact", "shots": 0, "amplitudes": [marked], "options": { "controls": "whole" } }), CAP).unwrap();
|
||||
assert_eq!(&out["state_hash"].as_str().unwrap()[..16], "454d8a59c85c788b", "{out}");
|
||||
}
|
||||
|
||||
/// Not asked, the bit-exact method runs standard gates: its bits are the ones
|
||||
/// `read` gives, as they were before a gate stood whole, and it runs no
|
||||
/// operation held whole.
|
||||
#[test]
|
||||
fn the_bit_exact_method_keeps_its_bits_unless_asked() {
|
||||
let src = grover(9, 2);
|
||||
let (_, out) = plan_and_run(&json!({ "qasm": src, "method": "exact", "shots": 0, "amplitudes": ["1".repeat(9)] }), CAP).unwrap();
|
||||
let want = qasm::read(&src).unwrap().program.dyadic().unwrap().simulate().unwrap().statevector_hash();
|
||||
assert_eq!(out["state_hash"].as_str().unwrap(), want.iter().map(|b| format!("{b:02x}")).collect::<String>(), "{out}");
|
||||
assert!(out.get("operations").is_none(), "{out}");
|
||||
let (_, whole) = plan_and_run(&json!({ "qasm": src, "method": "exact", "shots": 0, "amplitudes": ["1".repeat(9)], "options": { "controls": "whole" } }), CAP).unwrap();
|
||||
assert_ne!(whole["state_hash"], out["state_hash"], "the whole form rounds otherwise");
|
||||
assert_eq!(whole["gates"], out["gates"], "{whole}");
|
||||
}
|
||||
|
||||
/// Asked for standard gates, or placed on the fabric, the job takes them all,
|
||||
/// and its state is the old one's to the bit on the bit-exact method.
|
||||
#[test]
|
||||
fn a_job_can_ask_for_standard_gates() {
|
||||
let src = grover(8, 1);
|
||||
let run = |extra: Value| {
|
||||
let mut job = json!({ "qasm": src, "shots": 0, "amplitudes": ["11111111"] });
|
||||
job.as_object_mut().unwrap().extend(extra.as_object().unwrap().clone());
|
||||
plan_and_run(&job, CAP).unwrap().1
|
||||
};
|
||||
let whole = run(json!({ "method": "statevector" }));
|
||||
let standard = run(json!({ "method": "statevector", "options": { "controls": "standard" } }));
|
||||
assert!(whole["operations"].as_u64().unwrap() < standard["gates"].as_u64().unwrap());
|
||||
assert_eq!(whole["gates"], standard["gates"]);
|
||||
assert!(standard.get("operations").is_none(), "{standard}");
|
||||
let d = (whole["amplitudes"][0]["re"].as_f64().unwrap() - standard["amplitudes"][0]["re"].as_f64().unwrap()).abs();
|
||||
assert!(d < 1e-12, "{d}");
|
||||
let e = run(json!({ "method": "exact", "options": { "controls": "standard" } }));
|
||||
let want = qasm::read(&src).unwrap().program.dyadic().unwrap().simulate().unwrap().statevector_hash();
|
||||
assert_eq!(e["state_hash"].as_str().unwrap(), want.iter().map(|b| format!("{b:02x}")).collect::<String>());
|
||||
// A job that names a place on the fabric runs what every member runs.
|
||||
#[cfg(feature = "quantum_fabric")]
|
||||
{
|
||||
let cpu = run(json!({ "method": "statevector", "fabric": "cpu" }));
|
||||
assert_eq!(cpu["gates"], standard["gates"], "{cpu}");
|
||||
assert!(cpu.get("operations").is_none(), "{cpu}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A method that needs standard gates takes them, each answer the state
|
||||
/// vector's within its own accuracy, and says how many it ran.
|
||||
#[test]
|
||||
fn every_other_method_takes_standard_gates() {
|
||||
let src = "qubit[6] q;\nh q;\nctrl(3) @ p(0.7) q[0], q[1], q[2], q[3];\nctrl(4) @ x q[0], q[1], q[2], q[3], q[4];\n";
|
||||
let standard = qasm::read(src).unwrap().program.ops.len();
|
||||
let bits = "011111";
|
||||
let (_, sv) = plan_and_run(&json!({ "qasm": src, "method": "statevector", "amplitudes": [bits] }), CAP).unwrap();
|
||||
assert_eq!((sv["operations"].as_u64(), sv["gates"].as_u64()), (Some(8), Some(standard as u64)));
|
||||
let a = |v: &Value, k: &str| v["amplitudes"][0][k].as_f64().unwrap();
|
||||
for method in ["mps", "tensor_network"] {
|
||||
let (p, out) = plan_and_run(&json!({ "qasm": src, "method": method, "amplitudes": [bits] }), CAP).unwrap();
|
||||
assert_eq!(p.method, method);
|
||||
assert_eq!(out["gates"].as_u64(), Some(standard as u64), "{method}: {out}");
|
||||
assert!(out.get("operations").is_none(), "{method}: {out}");
|
||||
assert!((a(&out, "re") - a(&sv, "re")).abs() < 1e-9 && (a(&out, "im") - a(&sv, "im")).abs() < 1e-9, "{method}: {out}, {sv}");
|
||||
}
|
||||
let (p, out) = plan_and_run(&json!({ "qasm": src, "noise": { "depolarizing_1q": 0.0001 }, "shots": 16 }), CAP).unwrap();
|
||||
assert_eq!((p.method.as_str(), out["gates"].as_u64()), ("density_matrix", Some(standard as u64)), "{out}");
|
||||
let (p, out) = plan_and_run(&json!({ "qasm": src, "method": "pauli", "observables": ["Z0"] }), CAP).unwrap();
|
||||
assert_eq!((p.method.as_str(), out["gates"].as_u64()), ("pauli", Some(standard as u64)), "{out}");
|
||||
// A compile writes standard gates: the job runs what it wrote.
|
||||
let (_, out) = plan_and_run(&json!({ "qasm": src, "method": "statevector", "shots": 4, "compile": { "basis": "standard", "level": 0 } }), CAP).unwrap();
|
||||
assert!(out.get("operations").is_none() && out["compiled"].is_object(), "{out}");
|
||||
}
|
||||
|
||||
/// Past what standard gates can hold: 20 controls run whole on the state
|
||||
/// vector, on the bit-exact method asked to, and under `auto`. Everything that
|
||||
/// needs standard gates refuses, naming why (the bit-exact method by default
|
||||
/// among them), and a Clifford check takes one gate to say no.
|
||||
#[test]
|
||||
fn past_the_standard_form_only_the_state_vectors_take_it() {
|
||||
let (n, marked) = (21, "1".repeat(21));
|
||||
let src = format!("qubit[{n}] q;\nx q;\nctrl(20) @ z {};\nctrl(20) @ x {};\n", wide(n), wide(n));
|
||||
for (method, options) in [("exact", json!({ "controls": "whole" })), ("statevector", json!({})), ("auto", json!({}))] {
|
||||
let job = json!({ "qasm": src, "method": method, "shots": 0, "amplitudes": [marked], "options": options });
|
||||
let (p, out) = plan_and_run(&job, CAP).unwrap();
|
||||
assert!(runs_whole(&p.method, &job), "{method}: {}", p.why);
|
||||
assert_eq!(out["operations"], 23, "{out}");
|
||||
assert_eq!(out["gates"].as_u64(), Some(qasm::read_whole(&src).unwrap().program.standard_gates() as u64), "{out}");
|
||||
}
|
||||
let e = plan(&json!({ "qasm": src, "method": "exact", "amplitudes": [marked] }), CAP).unwrap_err();
|
||||
assert!(e.contains("Q208"), "{e}");
|
||||
for method in ["mps", "tensor_network", "stabilizer", "pauli"] {
|
||||
let e = plan(&json!({ "qasm": src, "method": method, "amplitudes": [marked], "observables": if method == "pauli" { json!(["Z0"]) } else { json!([]) } }), CAP).unwrap_err();
|
||||
assert!(e.contains("J011") || e.contains("Q208") || e.contains("Clifford") || e.contains("expectation"), "{method}: {e}");
|
||||
}
|
||||
let e = plan(&json!({ "qasm": src, "method": "mps", "amplitudes": [marked] }), CAP).unwrap_err();
|
||||
assert!(e.contains("Q208") && e.contains("at most 16 controls"), "{e}");
|
||||
let e = plan(&json!({ "qasm": src, "method": "statevector", "amplitudes": [marked], "options": { "controls": "standard" } }), CAP).unwrap_err();
|
||||
assert!(e.contains("Q208"), "{e}");
|
||||
let e = plan(&json!({ "qasm": src, "amplitudes": [marked], "noise": { "dephasing": 0.01 } }), CAP).unwrap_err();
|
||||
assert!(e.contains("Q208"), "{e}");
|
||||
// lint says what a job takes.
|
||||
let l = crate::engine::job::lint(&src);
|
||||
assert_eq!((l["ok"].as_bool(), l["operations"].as_u64()), (Some(true), Some(23)), "{l}");
|
||||
assert!(l["gates"].as_u64().unwrap() > 1 << 22, "{l}");
|
||||
}
|
||||
|
||||
/// Between measurements too: a gate in a branch runs whole, each expectation
|
||||
/// the standard form's within rounding, the branches' counts equal.
|
||||
#[test]
|
||||
fn a_gate_in_a_program_that_measures_runs_whole() {
|
||||
let src = "qubit[5] q;\nbit[1] c;\nh q;\nc[0] = measure q[0];\nif (c[0] == 1) { ctrl(3) @ x q[1], q[2], q[3], q[4]; }\nctrl(3) @ z q[1], q[2], q[3], q[4];\nh q[4];\n";
|
||||
let job = |extra: Value| {
|
||||
let mut j = json!({ "qasm": src, "method": "statevector", "observables": ["Z4", "X1 Z2"], "shots": 20000, "seed": 3 });
|
||||
j.as_object_mut().unwrap().extend(extra.as_object().unwrap().clone());
|
||||
j
|
||||
};
|
||||
let (_, whole) = plan_and_run(&job(json!({})), CAP).unwrap();
|
||||
let (_, standard) = plan_and_run(&job(json!({ "options": { "controls": "standard" } })), CAP).unwrap();
|
||||
assert_eq!(whole["operations"].as_u64(), Some(8), "{whole}");
|
||||
assert_eq!(whole["gates"], standard["gates"], "{whole}");
|
||||
assert!(whole["dynamic"].is_object(), "{whole}");
|
||||
for i in 0..2 {
|
||||
let (a, b) = (whole["expectations"][i]["value"].as_f64().unwrap(), standard["expectations"][i]["value"].as_f64().unwrap());
|
||||
assert!((a - b).abs() < 1e-12, "{a} against {b}");
|
||||
}
|
||||
assert_eq!(whole["counts"].as_object().unwrap().len(), standard["counts"].as_object().unwrap().len());
|
||||
}
|
||||
|
||||
/// The intent's check reads standard gates, whatever the run took.
|
||||
#[test]
|
||||
fn a_verdict_holds_a_whole_run_to_its_intent() {
|
||||
let n = 5;
|
||||
let src = format!("qubit[{n}] q;\nx q;\nctrl({}) @ x {};\n", n - 1, wide(n));
|
||||
let want = json!({ "distribution": { "01111": 1.0 } });
|
||||
let (_, out) = plan_and_run(&json!({ "qasm": src, "method": "statevector", "shots": 8, "intent": want }), CAP).unwrap();
|
||||
assert_eq!(out["verdict"]["holds"], true, "{out}");
|
||||
assert!(out["gates"].as_u64().unwrap() > out["operations"].as_u64().unwrap());
|
||||
}
|
||||
|
||||
/// A registered method, which takes the program as the reader's standard
|
||||
/// gates, never meets a gate held whole, and `auto` passes over it for the
|
||||
/// methods that do not need them.
|
||||
#[test]
|
||||
fn a_registered_method_sees_standard_gates() {
|
||||
use crate::engine::registry::{Component, Job, Method, Question, Registry};
|
||||
struct Looks;
|
||||
impl Method for Looks {
|
||||
fn name(&self) -> &str {
|
||||
"looks"
|
||||
}
|
||||
fn version(&self) -> &str {
|
||||
"1.0.0"
|
||||
}
|
||||
fn classes(&self) -> Vec<String> {
|
||||
vec!["exact".into()]
|
||||
}
|
||||
fn descriptor(&self) -> Value {
|
||||
json!({})
|
||||
}
|
||||
fn auto_rank(&self, _: Question) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
fn plan(&self, job: &Job<'_>) -> Result<Plan, String> {
|
||||
let r = job.request()?;
|
||||
assert!(!r.program.has_whole());
|
||||
Ok(Plan { method: "looks".into(), why: format!("{} standard gates", r.program.ops.len()), memory_bytes: 1, work: 1, qubits: r.program.n_qubits, target: None, metered: false })
|
||||
}
|
||||
fn run(&self, job: &Job<'_>, _: &Plan, out: &mut Value) -> Result<(), String> {
|
||||
let r = job.request()?;
|
||||
assert!(!r.program.has_whole());
|
||||
out["accuracy"] = json!({ "class": "exact" });
|
||||
out["amplitudes"] = json!([]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
let mut reg = Registry::with_builtins();
|
||||
reg.register(Component::method(Looks)).unwrap();
|
||||
let src = grover(6, 1);
|
||||
let job = json!({ "qasm": src, "method": "looks", "amplitudes": ["111111"] });
|
||||
let p = reg.plan(&job, CAP).unwrap();
|
||||
assert!(p.why.starts_with(&format!("{} standard gates", qasm::read(&src).unwrap().program.ops.len())), "{}", p.why);
|
||||
let out = reg.run(&job, &p).unwrap();
|
||||
assert!(out.get("operations").is_none(), "{out}");
|
||||
assert_eq!(out["gates"].as_u64().unwrap() as usize, qasm::read(&src).unwrap().program.ops.len());
|
||||
}
|
||||
|
||||
/// The phase by which the bit-exact method's amplitudes differ from
|
||||
/// OpenQASM's is stated, exactly, and held to the state vector's amplitudes:
|
||||
/// multiplied in, they agree within the method's resolution; left out, they
|
||||
/// differ by the phase. A program with none has no such member.
|
||||
#[test]
|
||||
fn a_bit_exact_result_states_its_global_phase() {
|
||||
for (body, phase) in [
|
||||
("rz(2 * pi) q[0];\n", Some(std::f64::consts::PI)),
|
||||
("rz(pi / 4) q[0];\n", Some(-std::f64::consts::FRAC_PI_8)),
|
||||
("h q[0];\nrx(pi / 2) q[1];\nry(pi / 4) q[1];\nu(pi / 2, pi / 4, pi / 8) q[0];\nrzz(pi / 2) q[0], q[1];\nrxx(pi / 4) q[0], q[1];\nryy(pi / 8) q[0], q[1];\n", None),
|
||||
("h q[0];\nctrl @ rz(pi / 4) q[0], q[1];\nu(0, 0, 0) q[1];\ncp(pi / 2) q[0], q[1];\n", Some(0.0)),
|
||||
] {
|
||||
let src = format!("qubit[2] q;\n{body}");
|
||||
let bits = ["00", "01", "10", "11"];
|
||||
let (_, exact) = plan_and_run(&json!({ "qasm": src, "method": "exact", "shots": 0, "amplitudes": bits }), CAP).unwrap();
|
||||
let (_, sv) = plan_and_run(&json!({ "qasm": src, "method": "statevector", "shots": 0, "amplitudes": bits }), CAP).unwrap();
|
||||
let stated = exact["accuracy"]["global_phase"]["radians"].as_f64();
|
||||
match phase {
|
||||
Some(0.0) => assert!(exact["accuracy"].get("global_phase").is_none(), "{exact}"),
|
||||
Some(p) => assert!((stated.unwrap() - p).abs() < 1e-14, "{exact}"),
|
||||
None => assert!(stated.is_some(), "{exact}"),
|
||||
}
|
||||
let phi = stated.unwrap_or(0.0);
|
||||
let (c, s) = (phi.cos(), phi.sin());
|
||||
for i in 0..4 {
|
||||
let (ar, ai) = (exact["amplitudes"][i]["re"].as_f64().unwrap(), exact["amplitudes"][i]["im"].as_f64().unwrap());
|
||||
let (br, bi) = (sv["amplitudes"][i]["re"].as_f64().unwrap(), sv["amplitudes"][i]["im"].as_f64().unwrap());
|
||||
assert!((ar * c - ai * s - br).abs() < 1e-6 && (ar * s + ai * c - bi).abs() < 1e-6, "{body}: amplitude {i}: {exact} against {sv}");
|
||||
}
|
||||
}
|
||||
// Whole, the phase of a rotation under controls stays in the amplitudes.
|
||||
let src = "qubit[3] q;\nx q[0];\nx q[1];\nh q[2];\nctrl(2) @ rz(pi / 2) q[0], q[1], q[2];\n";
|
||||
let bits = ["011", "111"];
|
||||
let (_, exact) = plan_and_run(&json!({ "qasm": src, "method": "exact", "shots": 0, "amplitudes": bits, "options": { "controls": "whole" } }), CAP).unwrap();
|
||||
let (_, sv) = plan_and_run(&json!({ "qasm": src, "method": "statevector", "shots": 0, "amplitudes": bits }), CAP).unwrap();
|
||||
assert!(exact["operations"].is_u64() && exact["accuracy"].get("global_phase").is_none(), "{exact}");
|
||||
for i in 0..2 {
|
||||
for k in ["re", "im"] {
|
||||
assert!((exact["amplitudes"][i][k].as_f64().unwrap() - sv["amplitudes"][i][k].as_f64().unwrap()).abs() < 1e-6, "{exact} against {sv}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An angle below the bit-exact table no longer runs as no phase in silence.
|
||||
#[test]
|
||||
fn an_angle_below_the_table_is_stated() {
|
||||
let src = "qubit[1] q;\nh q[0];\np(2 * pi / 2**50) q[0];\n";
|
||||
let job = json!({ "qasm": src, "method": "exact", "shots": 0, "amplitudes": ["1"] });
|
||||
let p = plan(&job, CAP).unwrap();
|
||||
assert!(p.why.contains("below its table") && p.why.contains("no phase"), "{}", p.why);
|
||||
let (_, out) = plan_and_run(&job, CAP).unwrap();
|
||||
assert_eq!(out["accuracy"]["below_table"]["angles"], 1, "{out}");
|
||||
assert!(out["accuracy"]["below_table"]["note"].as_str().unwrap().contains("no phase"));
|
||||
// A dyadic angle is not.
|
||||
let (_, out) = plan_and_run(&json!({ "qasm": "qubit[1] q;\nh q[0];\np(2 * pi / 2**32) q[0];\n", "method": "exact", "shots": 0, "amplitudes": ["1"] }), CAP).unwrap();
|
||||
assert!(out["accuracy"].get("below_table").is_none(), "{out}");
|
||||
// An angle the table's reader cannot place is still refused.
|
||||
let e = plan(&json!({ "qasm": "qubit[1] q;\np(2 * pi / 2**40) q[0];\n", "method": "exact", "shots": 16 }), CAP).unwrap_err();
|
||||
assert!(e.contains("Q500"), "{e}");
|
||||
}
|
||||
|
||||
/// The 30-qubit QFT at `max_bond` 256, which the estimate of the bond asked for
|
||||
/// refused: named, it runs, on the bond it reaches, and the plan says how. Under
|
||||
/// `auto` nothing moves, and an estimate too large at the smallest bond that
|
||||
/// entangles is refused still.
|
||||
#[test]
|
||||
fn a_named_matrix_product_job_is_held_to_the_bond_it_reaches() {
|
||||
let n = 30usize;
|
||||
let mut src = format!("qubit[{n}] q;\n");
|
||||
for q in 0..n {
|
||||
src += &format!("h q[{q}];\n");
|
||||
for t in q + 1..n {
|
||||
src += &format!("cp(pi / 2**{}) q[{t}], q[{q}];\n", t - q);
|
||||
}
|
||||
}
|
||||
let zero = "0".repeat(n);
|
||||
let job = json!({ "qasm": src, "method": "mps", "amplitudes": [zero], "options": { "max_bond": 256 } });
|
||||
let p = plan(&job, CAP).unwrap();
|
||||
assert!(p.metered && p.why.contains("metered"), "{p:?}");
|
||||
let (_, out) = plan_and_run(&job, CAP).unwrap();
|
||||
assert_eq!(out["accuracy"]["max_bond"], 256, "{out}");
|
||||
let used = out["accuracy"]["max_bond_used"].as_u64().unwrap();
|
||||
assert!(used < 256, "reached {used}");
|
||||
let want = 2f64.powf(-(n as f64) / 2.0);
|
||||
assert!((out["amplitudes"][0]["re"].as_f64().unwrap() - want).abs() < 1e-9, "{out}");
|
||||
// Below the budget the plan is what it was: nothing metered to say.
|
||||
let small = plan(&json!({ "qasm": src, "method": "mps", "amplitudes": [zero], "options": { "max_bond": 32 } }), CAP).unwrap();
|
||||
assert!(small.work < MPS_MAX_WORK && !small.why.contains("metered"), "{small:?}");
|
||||
// Unnamed, a job is held to the bound, and told what naming the method does.
|
||||
let r = read(&json!({ "qasm": src, "method": "auto", "amplitudes": [zero], "options": { "max_bond": 256 } })).unwrap();
|
||||
let e = try_method("mps", &r, &Err("".into()), &Err("".into()), CAP).unwrap_err();
|
||||
assert!(e.contains("name method mps"), "{e}");
|
||||
// More than a job may take at the smallest bond there is: refused now.
|
||||
let far = "qubit[4096] q;\nfor uint i in [0:799999] { cx q[0], q[4095]; }\n";
|
||||
let e = plan(&json!({ "qasm": far, "method": "mps", "shots": 1 }), CAP).unwrap_err();
|
||||
assert!(e.contains("[J011]") && e.contains("more work than one job may take"), "{e}");
|
||||
}
|
||||
|
||||
/// A gate whose standard gates would pass what a program may hold is refused
|
||||
/// before any is written, wherever they would be: a few hundred bytes of text
|
||||
/// once asked the Clifford check for 2^42 gates, and the planner held a job's
|
||||
/// thread while its memory grew without limit. `p(0)` under 24 controls stands
|
||||
/// for 2^25 gates, and twenty of them under 16 for 2^21 more than a program
|
||||
/// holds; each such check answers at once, and a method that does not read the
|
||||
/// Clifford or bit-exact circuit never writes it.
|
||||
#[test]
|
||||
fn a_gate_too_large_for_standard_gates_is_refused_before_they_are_written() {
|
||||
let grover = |n: usize| format!("qubit[{n}] q;\nh q;\nctrl({}) @ z {};\n", n - 1, wide(n));
|
||||
let cases = [
|
||||
format!("qubit[42] q;\nctrl(41) @ z {};\n", wide(42)),
|
||||
format!("qubit[42] q;\nctrl(41) @ x {};\n", wide(42)),
|
||||
format!("qubit[40] q;\nctrl(39) @ t {};\n", wide(40)),
|
||||
grover(42),
|
||||
format!("qubit[25] q;\nctrl(24) @ p(0) {};\n", wide(25)),
|
||||
format!("qubit[17] q;\nfor uint i in [0:19] {{ ctrl(16) @ p(0) {}; }}\n", wide(17)),
|
||||
];
|
||||
for src in &cases {
|
||||
let t = std::time::Instant::now();
|
||||
let p = qasm::read_whole(src).unwrap().program;
|
||||
let e = p.clifford().unwrap_err();
|
||||
assert!(e.contains("Q208") || e.contains("Q210"), "{src}: {e}");
|
||||
for method in ["statevector", "stabilizer", "auto"] {
|
||||
let _ = plan(&json!({ "qasm": src, "method": method, "shots": 16 }), CAP);
|
||||
}
|
||||
let _ = plan(&json!({ "qasm": src, "method": "statevector", "shots": 16, "dry_run": true }), CAP);
|
||||
// A bound far past any host's load, and far below what writing them takes.
|
||||
assert!(t.elapsed() < std::time::Duration::from_secs(120), "{src}: {:?}", t.elapsed());
|
||||
}
|
||||
}
|
||||
|
||||
/// The whole gate's Clifford check reads the phases its standard gates take
|
||||
/// exactly: θ/2^(m−1) under m qubits is no quarter turn unless θ makes it one,
|
||||
/// however small, so `p(1e-7)` under 16 controls is not taken for the
|
||||
/// identity, while `p(0)` under them is the identity, and `rz(2π)` under two is
|
||||
/// a controlled Z on them.
|
||||
#[test]
|
||||
fn a_tiny_phase_under_many_controls_is_not_read_as_clifford() {
|
||||
let q = wide(17);
|
||||
let not = qasm::read_whole(&format!("qubit[17] q;\nctrl(16) @ p(1e-7) {q};\n")).unwrap().program;
|
||||
assert!(not.clifford().is_err(), "a phase of 1e-7 under 16 controls taken for Clifford");
|
||||
let id = qasm::read_whole(&format!("qubit[17] q;\nctrl(16) @ p(0) {q};\n")).unwrap().program;
|
||||
assert!(id.clifford().is_ok());
|
||||
let cz = qasm::read_whole("qubit[3] q;\nctrl(2) @ rz(2 * pi) q[0], q[1], q[2];\n").unwrap().program;
|
||||
assert!(cz.clifford().is_ok());
|
||||
}
|
||||
|
||||
/// A verdict with no intent needs no standard form: the run it states goes on
|
||||
/// past 16 controls, and the verdict of the text says so too. An intent is
|
||||
/// checked in standard gates, so a job with one is refused where they cannot
|
||||
/// hold it, on its line.
|
||||
#[test]
|
||||
fn a_verdict_with_no_intent_holds_a_run_past_the_standard_form() {
|
||||
let (n, marked) = (21, "1".repeat(21));
|
||||
let src = format!("qubit[{n}] q;\nx q;\nctrl(20) @ z {};\n", wide(n));
|
||||
let job = json!({ "qasm": src, "method": "statevector", "shots": 0, "amplitudes": [marked], "verdict": true });
|
||||
let (_, out) = plan_and_run(&job, CAP).unwrap();
|
||||
assert_eq!((out["verdict"]["level"].as_str(), out["operations"].as_u64()), (Some("executed"), Some(22)), "{out}");
|
||||
assert_eq!(crate::engine::verdict::of_text(&src)["parsed"]["ok"], true);
|
||||
let e = plan(&json!({ "qasm": src, "shots": 16, "intent": { "distribution": { "0": 1.0 } } }), CAP).unwrap_err();
|
||||
assert!(e.starts_with("line 3:") && e.contains("Q208"), "{e}");
|
||||
}
|
||||
|
||||
/// A method that needs standard gates, named, refuses as the reader does: on
|
||||
/// the line, with the reader's code.
|
||||
#[test]
|
||||
fn a_named_method_on_standard_gates_refuses_as_the_reader_does() {
|
||||
let src = format!("qubit[18] q;\nh q;\n\nctrl(17) @ z {};\n", wide(18));
|
||||
for method in ["exact", "mps", "pauli", "stabilizer", "tensor_network"] {
|
||||
let job = json!({ "qasm": src, "method": method, "amplitudes": ["0".repeat(18)], "observables": if method == "pauli" { json!(["Z0"]) } else { json!([]) } });
|
||||
let e = plan(&job, CAP).unwrap_err();
|
||||
assert!(e.starts_with("line 4:"), "{method}: {e}");
|
||||
assert_eq!(crate::engine::refusal::refusal_json(&e)["code"], "Q208", "{method}: {e}");
|
||||
}
|
||||
let many = format!("qubit[17] q;\nfor uint i in [0:99] {{\n ctrl(16) @ z {};\n}}\n", wide(17));
|
||||
let e = plan(&json!({ "qasm": many, "method": "exact", "shots": 16 }), CAP).unwrap_err();
|
||||
assert!(e.starts_with("line ") && e.contains("Q210"), "{e}");
|
||||
assert_eq!(crate::engine::refusal::refusal_json(&e)["code"], "Q210", "{e}");
|
||||
}
|
||||
|
||||
/// lint counts a dynamic program's steps as it counts its gates: in standard
|
||||
/// gates, what the reader's `read` holds.
|
||||
#[test]
|
||||
fn lint_counts_a_program_s_steps_in_standard_gates() {
|
||||
let src = "qubit[5] q;\nbit[1] c;\nh q;\nc[0] = measure q[0];\nif (c[0] == 1) { ctrl(3) @ x q[1], q[2], q[3], q[4]; }\nctrl(3) @ z q[1], q[2], q[3], q[4];\n";
|
||||
let l = crate::engine::job::lint(src);
|
||||
let p = qasm::read(src).unwrap().program;
|
||||
assert_eq!(l["gates"].as_u64(), Some(p.ops.len() as u64), "{l}");
|
||||
assert_eq!(l["dynamic_steps"].as_u64(), Some(p.dynamic.as_ref().unwrap().len() as u64), "{l}");
|
||||
}
|
||||
|
||||
/// `exact` past 16 controls is told how to ask, by the job and by the reader.
|
||||
#[test]
|
||||
fn the_refusal_past_16_controls_names_the_option() {
|
||||
let src = format!("qubit[18] q;\nctrl(17) @ z {};\n", wide(18));
|
||||
let e = plan(&json!({ "qasm": src, "method": "exact", "amplitudes": ["0".repeat(18)] }), CAP).unwrap_err();
|
||||
assert!(e.contains("\"controls\": \"whole\""), "{e}");
|
||||
let e = qasm::read_whole(&src).unwrap().program.lowered().unwrap_err().to_string();
|
||||
assert!(e.contains("\"controls\": \"whole\""), "{e}");
|
||||
}
|
||||
|
||||
/// `options.controls` is read: another value is refused, and so is `whole`
|
||||
/// where the job cannot hold a gate whole; the methods list names it.
|
||||
#[test]
|
||||
fn options_controls_is_read_and_not_ignored() {
|
||||
let src = "qubit[4] q;\nh q;\nctrl(2) @ t q[0], q[1], q[2];\n";
|
||||
for bad in [json!("Whole"), json!("wholes"), json!(1), json!(true)] {
|
||||
let e = plan(&json!({ "qasm": src, "method": "exact", "shots": 8, "options": { "controls": bad } }), CAP).unwrap_err();
|
||||
assert!(e.contains("J003") && e.contains("controls"), "{bad}: {e}");
|
||||
}
|
||||
let whole = |extra: Value| {
|
||||
let mut j = json!({ "qasm": src, "shots": 8, "options": { "controls": "whole" } });
|
||||
j.as_object_mut().unwrap().extend(extra.as_object().unwrap().clone());
|
||||
j
|
||||
};
|
||||
for extra in [json!({ "compile": { "epsilon": 0.001 } }), json!({ "noise": { "dephasing": 0.01 } }), json!({ "fabric": "cpu" }), json!({ "method": "mps" }), json!({ "method": "stabilizer" }), json!({ "method": "pauli", "observables": ["Z0"] })] {
|
||||
let e = plan(&whole(extra.clone()), CAP).unwrap_err();
|
||||
assert!(e.contains("J003") && e.contains("whole"), "{extra}: {e}");
|
||||
}
|
||||
for ok in [json!({ "method": "exact" }), json!({ "method": "statevector" }), json!({ "method": "auto" })] {
|
||||
plan(&whole(ok.clone()), CAP).unwrap_or_else(|e| panic!("{ok}: {e}"));
|
||||
}
|
||||
plan(&json!({ "qasm": src, "shots": 8, "noise": { "dephasing": 0.01 }, "options": { "controls": "standard" } }), CAP).unwrap();
|
||||
for m in ["exact", "statevector"] {
|
||||
assert!(method_text(m)["options"]["controls"].is_string(), "{m}: {}", method_text(m));
|
||||
}
|
||||
}
|
||||
|
||||
/// What a bit-exact result's phase is held to: method statevector's state, of
|
||||
/// the program that ran. `U` and `gphase` are where the language's own state
|
||||
/// differs from it, and the note says which it is.
|
||||
#[test]
|
||||
fn the_stated_phase_is_relative_to_the_state_vector() {
|
||||
for body in ["U(pi / 2, 0, 0) q[0];\n", "u3(pi / 2, pi / 4, pi / 8) q[0];\n", "gphase(pi / 2);\nh q[0];\nrz(pi / 4) q[0];\n"] {
|
||||
let src = format!("qubit[1] q;\n{body}");
|
||||
let bits = ["0", "1"];
|
||||
let (_, exact) = plan_and_run(&json!({ "qasm": src, "method": "exact", "shots": 0, "amplitudes": bits }), CAP).unwrap();
|
||||
let (_, sv) = plan_and_run(&json!({ "qasm": src, "method": "statevector", "shots": 0, "amplitudes": bits }), CAP).unwrap();
|
||||
let g = &exact["accuracy"]["global_phase"];
|
||||
let note = g["note"].as_str().unwrap();
|
||||
assert!(note.contains("method statevector") && note.contains("gphase") && note.contains("U") && !note.contains("OpenQASM's state"), "{note}");
|
||||
let (c, s) = (g["radians"].as_f64().unwrap().cos(), g["radians"].as_f64().unwrap().sin());
|
||||
for i in 0..2 {
|
||||
let (ar, ai) = (exact["amplitudes"][i]["re"].as_f64().unwrap(), exact["amplitudes"][i]["im"].as_f64().unwrap());
|
||||
let (br, bi) = (sv["amplitudes"][i]["re"].as_f64().unwrap(), sv["amplitudes"][i]["im"].as_f64().unwrap());
|
||||
assert!((ar * c - ai * s - br).abs() < 1e-6 && (ar * s + ai * c - bi).abs() < 1e-6, "{body}: {exact} against {sv}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Asked for, the bit-exact method's gate under controls is the state vector's,
|
||||
/// gate by gate, in every amplitude.
|
||||
#[test]
|
||||
fn the_bit_exact_whole_form_gives_the_state_vector_s_amplitudes() {
|
||||
let bits: Vec<String> = (0..16).map(|i| format!("{i:04b}")).collect();
|
||||
for g in ["x", "y", "z", "h", "s", "sdg", "t", "tdg", "sx", "sxdg", "p(pi / 8)", "rz(pi / 4)", "rx(pi / 4)", "ry(pi / 4)", "u(pi / 2, pi / 4, pi / 8)"] {
|
||||
let src = format!("qubit[4] q;\nh q;\nt q[2];\nh q[2];\nt q[3];\nctrl(2) @ {g} q[0], q[1], q[2];\nctrl(3) @ {g} q[3], q[0], q[1], q[2];\nh q[2];\n");
|
||||
let job = |method: &str, options: Value| json!({ "qasm": src, "method": method, "shots": 0, "amplitudes": bits, "options": options });
|
||||
let (_, exact) = plan_and_run(&job("exact", json!({ "controls": "whole" })), CAP).unwrap();
|
||||
let (_, sv) = plan_and_run(&job("statevector", json!({})), CAP).unwrap();
|
||||
assert!(exact["operations"].as_u64().is_some(), "{g}: {exact}");
|
||||
for i in 0..16 {
|
||||
for k in ["re", "im"] {
|
||||
let (a, b) = (exact["amplitudes"][i][k].as_f64().unwrap(), sv["amplitudes"][i][k].as_f64().unwrap());
|
||||
assert!((a - b).abs() < 1e-6, "{g}: amplitude {i}, {k}: {a} against {b}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Every number the bit-exact accuracy states, read back.
|
||||
#[test]
|
||||
fn a_bit_exact_result_s_accuracy_members_hold_what_they_say() {
|
||||
let run = |src: &str| plan_and_run(&json!({ "qasm": src, "method": "exact", "shots": 0, "amplitudes": ["0"] }), CAP).unwrap().1;
|
||||
let out = run("qubit[1] q;\nrz(pi / 4) q[0];\n");
|
||||
assert_eq!(out["accuracy"]["global_phase"]["turns_over_2_33"].as_u64(), Some((1 << 33) - (1 << 29)), "{out}");
|
||||
let out = run("qubit[1] q;\np(2 * pi / 2**50) q[0];\n");
|
||||
let want = std::f64::consts::TAU / 2f64.powi(50);
|
||||
assert!((out["accuracy"]["below_table"]["largest_radians"].as_f64().unwrap() / want - 1.0).abs() < 1e-3, "{out}");
|
||||
let out = run("qubit[1] q;\np(pi / 2 + 2 * pi / 2**48) q[0];\n");
|
||||
let want = std::f64::consts::TAU / 2f64.powi(48);
|
||||
let m = &out["accuracy"]["moved_angles"];
|
||||
assert_eq!(m["angles"], 1, "{out}");
|
||||
assert!((m["largest_radians"].as_f64().unwrap() / want - 1.0).abs() < 1e-2, "{out}");
|
||||
assert!(m["note"].as_str().unwrap().contains("1e-14"), "{out}");
|
||||
// A double's rounding of an angle on the grid moves nothing: angles whose
|
||||
// turn does not come back exactly, and an angle's report says none moved.
|
||||
let mut seen = 0;
|
||||
for k in 1..4000u64 {
|
||||
let m = k.wrapping_mul(2654435761) % (1 << 32);
|
||||
let at = m as f64 / (1u64 << 32) as f64;
|
||||
let angle = at * std::f64::consts::TAU;
|
||||
if (angle / std::f64::consts::TAU).rem_euclid(1.0) == at {
|
||||
continue;
|
||||
}
|
||||
seen += 1;
|
||||
let (_, r) = qasm::read(&format!("qubit[1] q;\np({angle:e}) q[0];\n")).unwrap().program.dyadic_with_report().unwrap();
|
||||
assert_eq!((r.moved, r.below_table), (0, 0), "{angle:e}");
|
||||
}
|
||||
assert!(seen > 10, "{seen}");
|
||||
}
|
||||
|
||||
/// A program that entangles, for a job the meter watches: `n` qubits, `layers`
|
||||
/// of rotations and a brickwork of cx.
|
||||
fn entangling(n: usize, layers: usize) -> String {
|
||||
let mut s = format!("qubit[{n}] q;\n");
|
||||
for l in 0..layers {
|
||||
s += "rx(0.3) q;\nry(0.4) q;\n";
|
||||
for k in (l % 2..n - 1).step_by(2) {
|
||||
s += &format!("cx q[{k}], q[{}];\n", k + 1);
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// A job the meter stops: at the bond the chain holds, with a refusal that says
|
||||
/// where, and for its shots, once the chain has run.
|
||||
#[test]
|
||||
fn the_meter_stops_a_job_that_passes_its_budget() {
|
||||
use crate::engine::run::MPS_BUDGET;
|
||||
use crate::quantum_mpsim::{Chain, MpsConfig};
|
||||
let (n, chi) = (14usize, 16u64);
|
||||
let src = entangling(n, 8);
|
||||
let job = json!({ "qasm": src, "method": "mps", "shots": 200, "options": { "max_bond": chi } });
|
||||
let (_, free) = plan_and_run(&job, CAP).unwrap();
|
||||
let used = free["accuracy"]["max_bond_used"].as_u64().unwrap();
|
||||
assert!(used > 4, "reached {used}");
|
||||
let p = qasm::read(&src).unwrap().program;
|
||||
let chain = Chain::run(n as u32, &p.local_matrices(), MpsConfig { max_bond: chi as usize, cutoff: 1e-12 }).unwrap();
|
||||
assert!(chain.work() > 8 * chain.report().updates, "{} units over {} updates", chain.work(), chain.report().updates);
|
||||
let plan = plan(&job, CAP).unwrap();
|
||||
let r = read(&job).unwrap();
|
||||
let at = |budget: u64| {
|
||||
MPS_BUDGET.set(Some(budget));
|
||||
let e = crate::engine::run::run(&job, &plan);
|
||||
MPS_BUDGET.set(None);
|
||||
e
|
||||
};
|
||||
let e = at(chain.work() / 2).unwrap_err();
|
||||
assert!(e.contains("[J011]") && e.contains("units of work") && e.contains("stopped"), "{e}");
|
||||
// The chain within it, its shots at the bond it reached not.
|
||||
let e = at(chain.work() + mps_shots_work(&r, 1) + 1).unwrap_err();
|
||||
assert!(e.contains("[J011]") && e.contains("shots"), "{e}");
|
||||
let answer = at(chain.work() + mps_shots_work(&r, used)).unwrap();
|
||||
assert_eq!(answer["counts"], free["counts"]);
|
||||
}
|
||||
|
||||
/// A plan for a job that runs metered says what it may take: the work the
|
||||
/// meter holds it to, so a dry run's time is the meter's, and that it is held.
|
||||
#[test]
|
||||
fn a_metered_plan_states_the_work_its_meter_allows() {
|
||||
let n = 30usize;
|
||||
let mut src = format!("qubit[{n}] q;\n");
|
||||
for q in 0..n {
|
||||
src += &format!("h q[{q}];\n");
|
||||
for t in q + 1..n {
|
||||
src += &format!("cp(pi / 2**{}) q[{t}], q[{q}];\n", t - q);
|
||||
}
|
||||
}
|
||||
let p = plan(&json!({ "qasm": src, "method": "mps", "amplitudes": ["0".repeat(n)], "options": { "max_bond": 256 } }), CAP).unwrap();
|
||||
assert_eq!((p.work, p.metered), (MPS_MAX_WORK, true), "{p:?}");
|
||||
assert!(p.why.contains("metered"), "{p:?}");
|
||||
let p = plan(&json!({ "qasm": src, "method": "mps", "amplitudes": ["0".repeat(n)], "options": { "max_bond": 32 } }), CAP).unwrap();
|
||||
assert!(!p.metered, "{p:?}");
|
||||
}
|
||||
|
|
@ -936,6 +936,8 @@ pub fn compile(p: &Program, o: &Options) -> Result<Compiled, String> {
|
|||
/// the level's own and before twirling and decoupling. A registered pass's
|
||||
/// record names it (`component`), and its check is the engine's.
|
||||
pub(crate) fn compile_in(reg: &Registry, p: &Program, o: &Options, extra: &[&str]) -> Result<Compiled, String> {
|
||||
// A compile writes standard gates, whatever form the program came in.
|
||||
let p = &*p.standard_form().map_err(String::from)?;
|
||||
if p.dynamic.is_some() {
|
||||
return Err("the program measures in the middle, resets after a gate or acts on what it measured; compiling takes programs whose measurements all come at the end".into());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,38 @@ impl Engine for Dense<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// [`Dense`] with each gate under many controls held as one.
|
||||
#[derive(Clone)]
|
||||
pub struct DenseOps<'a> {
|
||||
pub sv: StateVector,
|
||||
pub ops: &'a [crate::quantum_sv::SvOp],
|
||||
}
|
||||
|
||||
impl Engine for DenseOps<'_> {
|
||||
fn gate(&mut self, i: usize) -> Result<(), String> {
|
||||
match &self.ops[i] {
|
||||
crate::quantum_sv::SvOp::Gate(g) => self.sv.apply(g),
|
||||
crate::quantum_sv::SvOp::Controlled(c) => self.sv.apply_controlled(c),
|
||||
}
|
||||
.map_err(|e| format!("{e:?}"))
|
||||
}
|
||||
fn prob_one(&mut self, q: u32) -> Result<f64, String> {
|
||||
Ok(self.sv.probability_one(q).clamp(0.0, 1.0))
|
||||
}
|
||||
fn collapse(&mut self, q: u32, outcome: bool) -> Result<(), String> {
|
||||
match self.sv.collapse(q, outcome).map_err(|e| format!("{e:?}"))? {
|
||||
p if p > 0.0 => Ok(()),
|
||||
_ => Err(format!("qubit {q} cannot read {}", u8::from(outcome))),
|
||||
}
|
||||
}
|
||||
fn flip(&mut self, q: u32) -> Result<(), String> {
|
||||
self.sv.apply(&Matrix::x(q)).map_err(|e| format!("{e:?}"))
|
||||
}
|
||||
fn expectation(&self, string: &[(u32, char)]) -> Result<f64, String> {
|
||||
Ok(self.sv.expectation(&[(1.0, string.to_vec())]))
|
||||
}
|
||||
}
|
||||
|
||||
/// The stabilizer tableau, each op as its Clifford gates.
|
||||
#[derive(Clone)]
|
||||
pub struct Stabilizer<'a> {
|
||||
|
|
|
|||
|
|
@ -180,6 +180,8 @@ fn lower_op(o: &Op, out: &mut Vec<Step>) {
|
|||
/// `p` in the estimate's gate set (see the module); refused for a program
|
||||
/// whose steps jump back, which no count bounds.
|
||||
pub fn lower_program(p: &Program) -> Result<Lowered, String> {
|
||||
// Its ladders read the standard form's records.
|
||||
let p = &*p.standard_form().map_err(String::from)?;
|
||||
let n = p.n_qubits;
|
||||
// Each op's ladder, by the index of the controlled gate it belongs to.
|
||||
let mut owner: Vec<Option<usize>> = vec![None; p.ops.len()];
|
||||
|
|
|
|||
|
|
@ -158,7 +158,9 @@ pub(crate) fn admit(v: &Value, r: &Request, plan: &mut Plan) -> Result<(), Strin
|
|||
}
|
||||
};
|
||||
}
|
||||
let placed_method = plan.method == "statevector" && r.program.dynamic.is_none();
|
||||
// A gate held whole runs on the CPU: a member takes standard gates. A job
|
||||
// that names a place reads its program in them (catalog::may_run_whole).
|
||||
let placed_method = plan.method == "statevector" && r.program.dynamic.is_none() && !r.program.has_whole();
|
||||
if !placed_method {
|
||||
return match ask {
|
||||
Ask::Auto => Ok(()),
|
||||
|
|
|
|||
|
|
@ -161,6 +161,8 @@ pub(crate) fn read_fe(fe: &dyn FrontEnd, input: &[u8]) -> Result<Imported, Diagn
|
|||
/// program's measurements all come at the end, else the adaptive one, when
|
||||
/// none is given); other formats take none.
|
||||
pub fn write(format: Format, p: &Program, profile: Option<Profile>) -> Result<Exported, Diagnostic> {
|
||||
// Another format holds standard gates, whatever form the program came in.
|
||||
let p = &*p.standard_form()?;
|
||||
match format {
|
||||
Format::Qasm => {
|
||||
if p.dynamic.is_some() {
|
||||
|
|
|
|||
|
|
@ -466,7 +466,9 @@ pub fn did_of(key_hex: &str) -> Option<String> {
|
|||
/// gates, bits and measurements (and its steps, when it is dynamic), or
|
||||
/// `{"ok": false}` with the problem, its line, and its diagnostic code,
|
||||
/// column and length (`qasm::diag`). The warnings and notes the reader gave
|
||||
/// a program it read are under `diagnostics`, when there are any.
|
||||
/// a program it read are under `diagnostics`, when there are any. `gates` counts
|
||||
/// standard gates; where a gate under many controls would double in them, a job
|
||||
/// on the state vector runs it as one, and `operations` says how many it runs.
|
||||
pub fn lint(src: &str) -> Value {
|
||||
lint_with(src, &crate::qasm::Inputs::new())
|
||||
}
|
||||
|
|
@ -474,7 +476,7 @@ pub fn lint(src: &str) -> Value {
|
|||
/// [`lint`] for a generator read at `inputs`: a problem keeps the generator's
|
||||
/// line.
|
||||
pub fn lint_with(src: &str, inputs: &crate::qasm::Inputs) -> Value {
|
||||
match crate::qasm::read_with(src, inputs) {
|
||||
match crate::qasm::read_whole_with(src, inputs) {
|
||||
Ok(r) => {
|
||||
let p = &r.program;
|
||||
let mut v = json!({
|
||||
|
|
@ -483,8 +485,12 @@ pub fn lint_with(src: &str, inputs: &crate::qasm::Inputs) -> Value {
|
|||
"gates": p.ops.len(),
|
||||
"bits": p.n_bits,
|
||||
"measured": p.measured.len(),
|
||||
"dynamic_steps": p.dynamic.as_ref().map(Vec::len),
|
||||
"dynamic_steps": p.standard_steps().map(|n| n.min((1u128 << 53) - 1) as u64),
|
||||
});
|
||||
if p.has_whole() {
|
||||
v["gates"] = json!(p.standard_gates().min((1u128 << 53) - 1) as u64);
|
||||
v["operations"] = json!(p.ops.len());
|
||||
}
|
||||
if !r.diagnostics.is_empty() {
|
||||
v["diagnostics"] = Value::Array(r.diagnostics.iter().map(|d| d.to_json()).collect());
|
||||
}
|
||||
|
|
@ -634,7 +640,9 @@ pub(crate) mod tests {
|
|||
(r#"{"qasm":"OPENQASM 3.0; qubit[2] q; bit[2] c; rx(0.4) q[0]; c[0] = measure q[0]; if (c[0]) x q[1]; c[1] = measure q[1];","shots":200,"observables":["Z1"]}"#, "261d5a754f17fa22d4dc308cd98f2d8accd89dd646716d6992770ecbe5d52acf"),
|
||||
// Its result's compiled names its gate set basis (once target): d714ea13… before.
|
||||
(r#"{"qasm":"OPENQASM 3.0; qubit[1] q; rx(0.4) q[0];","shots":100,"compile":{"epsilon":0.001}}"#, "b17615c95113dc7e80acfcd8825d1b3a7954bf71a71d1a121e1e6327703eecb2"),
|
||||
(r#"{"qasm":"OPENQASM 3.0; qubit[30] q; rx(0.3) q; cx q[0], q[1];","shots":20,"method":"mps"}"#, "975d7a6a5107df2090c6a17422a8a467bff4027a7a0e84d6d3d3acda659f27fc"),
|
||||
// Its plan's work counts its one-qubit gates, as the meter it bounds
|
||||
// does (quantum_mpsim::WorkBound): 975d7a6a… before.
|
||||
(r#"{"qasm":"OPENQASM 3.0; qubit[30] q; rx(0.3) q; cx q[0], q[1];","shots":20,"method":"mps"}"#, "c5ebfc80f6a1b8f2784f275a434903fc81a9a363b4dbac4203a21f09497d4146"),
|
||||
(r#"{"qasm":"OPENQASM 3.0;\ninclude \"mine.inc\";\nqubit[2] q;\nh q;\n","shots":64}"#, "93092f1fa723c2d7b088e5b047efd6e5ccdc42e1a14657d88dfd44df7b84bd52"),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -460,6 +460,7 @@ pub fn plan(v: &Value, job_bytes: u64) -> Result<Plan, String> {
|
|||
work: p.plan.work,
|
||||
qubits: p.plan.qubits.len() as u32,
|
||||
target: None,
|
||||
metered: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ table! {
|
|||
|
||||
// The method.
|
||||
UNKNOWN_METHOD = "J010", Refused, "A method that does not exist: the methods list names them.";
|
||||
METHOD_REFUSES = "J011", Refused, "The method named cannot take this job: a gate it does not hold, more qubits, memory or work than it takes here, or an answer it does not give (noise it cannot take is `J061`); the message says which, and `auto` picks a method that can.";
|
||||
METHOD_REFUSES = "J011", Refused, "The method named cannot take this job: a gate it does not hold, more qubits, memory or work than it takes here, or an answer it does not give (noise it cannot take is `J061`); the message says which, and `auto` picks a method that can. A job on `mps` runs under a meter of the bonds it reaches, so this can also come from a job that has started: it stopped where its work passed what a job may take.";
|
||||
NO_METHOD = "J012", Refused, "No method takes this job: each method's reason is listed, so the job can be changed to suit one.";
|
||||
FABRIC = "J013", Refused, "A placement on the fabric (`fabric`) this host cannot honour: no member by that name, no certified accelerator member here, a state or a partition larger than a member holds, a partition count that is not a power of two, or a method or program the fabric's members do not run (the message says which, and what is here).";
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,17 @@ impl Method for BuiltinMethod {
|
|||
self.ranks[question_index(question)]
|
||||
}
|
||||
fn plan(&self, job: &Job<'_>) -> Result<Plan, String> {
|
||||
catalog::try_method(self.name, job.request()?, job.dyadic(), job.clifford(), job.job_bytes())
|
||||
// A method that runs gates whole takes the program as read. A tableau
|
||||
// needs standard gates only for a circuit that is Clifford, which the
|
||||
// first gate that is not settles without writing the rest.
|
||||
let request = if catalog::runs_whole(self.name, job.json()) || (self.name == "stabilizer" && job.clifford().is_err()) { job.as_read()? } else { job.request()? };
|
||||
// Each circuit is made only for the method that reads it: making one
|
||||
// writes the program's gates, which no other method needs.
|
||||
let unread = format!("{} does not read this circuit", self.name);
|
||||
let (no_dyadic, no_clifford) = (Err(unread.clone()), Err(unread));
|
||||
let dyadic = if self.name == "exact" { job.dyadic() } else { &no_dyadic };
|
||||
let clifford = if self.name == "stabilizer" { job.clifford() } else { &no_clifford };
|
||||
catalog::try_method(self.name, request, dyadic, clifford, job.job_bytes())
|
||||
}
|
||||
fn run(&self, job: &Job<'_>, plan: &Plan, out: &mut Value) -> Result<(), String> {
|
||||
crate::engine::run::run_builtin(self.name, job, plan, out)
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ pub const ACCURACY_CLASSES: [&str; 6] = ["bit-exact", "exact", "single", "bounde
|
|||
|
||||
/// The members of a job's result the engine writes; a registered method's
|
||||
/// run leaves them as they are.
|
||||
pub const ENGINE_MEMBERS: [&str; 13] = ["method", "qubits", "gates", "compiled", "repaired", "engine", "schema", "components", "receipt", "verdict", "provenance", "energy", "elapsed_ms"];
|
||||
pub const ENGINE_MEMBERS: [&str; 14] = ["method", "qubits", "gates", "operations", "compiled", "repaired", "engine", "schema", "components", "receipt", "verdict", "provenance", "energy", "elapsed_ms"];
|
||||
|
||||
/// What a component is.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
|
@ -542,6 +542,9 @@ pub struct Job<'a> {
|
|||
json: &'a Value,
|
||||
job_bytes: u64,
|
||||
request: Option<&'a Request>,
|
||||
/// The request in standard gates, written when a method asks for it, where
|
||||
/// `request` holds gates whole.
|
||||
standard: Option<&'a OnceCell<Result<Request, String>>>,
|
||||
channels: Vec<ChannelUse<'a>>,
|
||||
dyadic: OnceCell<Result<crate::quantum::Circuit, String>>,
|
||||
clifford: OnceCell<Result<Vec<crate::quantum_tableau::Clifford>, String>>,
|
||||
|
|
@ -550,7 +553,21 @@ pub struct Job<'a> {
|
|||
impl<'a> Job<'a> {
|
||||
pub(crate) fn new(registry: &'a Registry, json: &'a Value, job_bytes: u64, request: Option<&'a Request>, uses: &[(usize, Vec<f64>)]) -> Job<'a> {
|
||||
let channels = uses.iter().map(|(i, v)| ChannelUse { channel: &*registry.channels[*i].component, identity: ®istry.channels[*i].identity, values: v.clone() }).collect();
|
||||
Job { registry, json, job_bytes, request, channels, dyadic: OnceCell::new(), clifford: OnceCell::new() }
|
||||
Job { registry, json, job_bytes, request, standard: None, channels, dyadic: OnceCell::new(), clifford: OnceCell::new() }
|
||||
}
|
||||
|
||||
/// The job with `request` holding gates whole: [`Job::request`] then gives
|
||||
/// it in standard gates, written once, in `cell`.
|
||||
pub(crate) fn with_standard(mut self, cell: &'a OnceCell<Result<Request, String>>) -> Job<'a> {
|
||||
self.standard = Some(cell);
|
||||
self
|
||||
}
|
||||
|
||||
/// The request as it was read: what every method is asked about, whatever
|
||||
/// form it runs in. For a built-in method that runs a gate under many
|
||||
/// controls as one ([`super::catalog::runs_whole`]), the request it runs.
|
||||
pub(crate) fn as_read(&self) -> Result<&'a Request, String> {
|
||||
self.request.ok_or_else(|| "internal: this method reads its own program, and the job was not read".to_string())
|
||||
}
|
||||
|
||||
/// The registry the job runs in.
|
||||
|
|
@ -568,10 +585,14 @@ impl<'a> Job<'a> {
|
|||
self.job_bytes
|
||||
}
|
||||
|
||||
/// The job as the engine's reader read it. A method that reads its
|
||||
/// program itself is given none.
|
||||
/// The job as the engine's reader read it, its program in standard gates.
|
||||
/// A method that reads its program itself is given none.
|
||||
pub fn request(&self) -> Result<&'a Request, String> {
|
||||
self.request.ok_or_else(|| "internal: this method reads its own program, and the job was not read".to_string())
|
||||
let r = self.as_read()?;
|
||||
match self.standard {
|
||||
Some(cell) => cell.get_or_init(|| r.lowered()).as_ref().map_err(Clone::clone),
|
||||
None => Ok(r),
|
||||
}
|
||||
}
|
||||
|
||||
/// Every noise channel the job names with a value other than zero, in
|
||||
|
|
@ -586,11 +607,17 @@ impl<'a> Job<'a> {
|
|||
self.channels.iter().filter(|c| !c.identity.is_builtin())
|
||||
}
|
||||
|
||||
/// The program as the bit-exact simulator's circuit, or why not.
|
||||
/// The program as the bit-exact simulator's circuit, or why not: in the
|
||||
/// gates the method runs, standard ones unless the job asks for a gate held
|
||||
/// whole (`"options": {"controls": "whole"}`), when it is one gate of it.
|
||||
pub fn dyadic(&self) -> &Result<crate::quantum::Circuit, String> {
|
||||
self.dyadic.get_or_init(|| match self.request {
|
||||
Some(r) => r.program.dyadic(),
|
||||
None => Err("no program read".into()),
|
||||
self.dyadic.get_or_init(|| {
|
||||
let r = if super::catalog::whole_asked(self.json) { self.as_read() } else { self.request() };
|
||||
match r {
|
||||
Ok(r) => r.program.dyadic(),
|
||||
Err(_) if self.request.is_none() => Err("no program read".into()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ impl Method for BasisTable {
|
|||
if !r.observables.is_empty() {
|
||||
return Err("basis_table answers counts and amplitudes".into());
|
||||
}
|
||||
Ok(Plan { method: "basis_table".into(), why: format!("a permutation of basis states on {} qubits, followed bit by bit", r.program.n_qubits), memory_bytes: 1 << 20, work: r.program.ops.len() as u64, qubits: r.program.n_qubits, target: None })
|
||||
Ok(Plan { method: "basis_table".into(), why: format!("a permutation of basis states on {} qubits, followed bit by bit", r.program.n_qubits), memory_bytes: 1 << 20, work: r.program.ops.len() as u64, qubits: r.program.n_qubits, target: None, metered: false })
|
||||
}
|
||||
fn run(&self, job: &Job<'_>, _: &Plan, out: &mut Value) -> Result<(), String> {
|
||||
let r = job.request()?;
|
||||
|
|
@ -99,7 +99,7 @@ impl Method for Rogue {
|
|||
}
|
||||
fn plan(&self, job: &Job<'_>) -> Result<Plan, String> {
|
||||
let method = if self.0 == "rogue_plan" { "exact".to_string() } else { self.0.to_string() };
|
||||
Ok(Plan { method, why: "rogue".into(), memory_bytes: 1, work: 1, qubits: job.request()?.program.n_qubits, target: None })
|
||||
Ok(Plan { method, why: "rogue".into(), memory_bytes: 1, work: 1, qubits: job.request()?.program.n_qubits, target: None, metered: false })
|
||||
}
|
||||
fn run(&self, _: &Job<'_>, _: &Plan, out: &mut Value) -> Result<(), String> {
|
||||
match self.0 {
|
||||
|
|
@ -108,6 +108,10 @@ impl Method for Rogue {
|
|||
out["method"] = json!("exact");
|
||||
}
|
||||
"rogue_class" => out["accuracy"] = json!({ "class": "bit-exact" }),
|
||||
"rogue_operations" => {
|
||||
out["accuracy"] = json!({ "class": "exact" });
|
||||
out["operations"] = json!(1);
|
||||
}
|
||||
_ => out["accuracy"] = json!({ "class": "exact" }),
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -512,11 +516,11 @@ fn registration_refuses_what_it_cannot_hold() {
|
|||
#[test]
|
||||
fn a_registered_method_s_result_is_held_to_the_contract() {
|
||||
let mut h = Registry::with_builtins();
|
||||
for n in ["rogue_method", "rogue_class", "rogue_plan"] {
|
||||
for n in ["rogue_method", "rogue_class", "rogue_plan", "rogue_operations"] {
|
||||
h.register(Component::method(Rogue(n))).unwrap();
|
||||
}
|
||||
let job = |m: &str| json!({ "qasm": "OPENQASM 3.0;\nqubit[1] q;\nx q[0];\n", "shots": 1, "method": m });
|
||||
for m in ["rogue_method", "rogue_class", "rogue_plan"] {
|
||||
for m in ["rogue_method", "rogue_class", "rogue_plan", "rogue_operations"] {
|
||||
let e = h.plan_and_run(&job(m), CAP).unwrap_err();
|
||||
assert_eq!(codes_in(&e).last().copied(), Some("J064"), "{m}: {e}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,51 @@ fn index_of(bits: &str, n: u32, pos: impl Fn(u32) -> u32) -> usize {
|
|||
x
|
||||
}
|
||||
|
||||
// A test's budget for an `mps` run, in place of `catalog::MPS_MAX_WORK`.
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
pub(crate) static MPS_BUDGET: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
|
||||
}
|
||||
|
||||
/// The work an `mps` run is held to.
|
||||
fn mps_budget() -> u64 {
|
||||
#[cfg(test)]
|
||||
if let Some(b) = MPS_BUDGET.get() {
|
||||
return b;
|
||||
}
|
||||
catalog::MPS_MAX_WORK
|
||||
}
|
||||
|
||||
/// What the bit-exact method promises of a result, and what the lowering to
|
||||
/// its gates did to the program, so that neither is silent: the global phase
|
||||
/// by which its amplitudes differ from method statevector's, and the angles
|
||||
/// below its table that ran as no phase.
|
||||
fn exact_accuracy(report: &crate::qasm::DyadicReport) -> Value {
|
||||
let mut a = json!({ "class": "bit-exact" });
|
||||
if report.global_phase != 0 {
|
||||
a["global_phase"] = json!({
|
||||
"turns_over_2_33": report.global_phase,
|
||||
"radians": report.global_phase_radians(),
|
||||
"note": "method statevector's state of the program that ran is e^(i·radians) times these amplitudes: this method lowers rz, rx, ry, rxx, ryy, rzz and u to phase gates, each without that method's e^(−iθ/2). No measurement sees it; a gate under a control keeps its phase. Neither method holds the language's own phase where it differs: its U is e^(iθ/2) times the matrix both take as u, and the reader drops an uncontrolled gphase.",
|
||||
});
|
||||
}
|
||||
if report.below_table > 0 {
|
||||
a["below_table"] = json!({
|
||||
"angles": report.below_table,
|
||||
"largest_radians": report.largest_below_table,
|
||||
"note": "phase gates of the lowering (one angle written may be several) within 1e-14 of a turn of a whole turn are below every entry of the phase table and ran as no phase; this method's bound holds, and method statevector applies them",
|
||||
});
|
||||
}
|
||||
if report.moved > 0 {
|
||||
a["moved_angles"] = json!({
|
||||
"angles": report.moved,
|
||||
"largest_radians": report.largest_moved,
|
||||
"note": "phase gates of the lowering within the reader's tolerance, 1e-14 of a turn, of a dyadic angle, by more than a double's rounding, ran as that dyadic angle",
|
||||
});
|
||||
}
|
||||
a
|
||||
}
|
||||
|
||||
/// `⟨ψ|P|ψ⟩` on the bit-exact state, exactly: the fixed-point amplitudes
|
||||
/// multiplied and summed in 128-bit integers, then scaled once.
|
||||
fn expect_fixed(amps: &[crate::quantum::Amp], string: &[(u32, char)]) -> f64 {
|
||||
|
|
@ -244,7 +289,8 @@ fn run_job(reg: &Registry, request: &Value, plan: &Plan, passes: &mut Option<Val
|
|||
if request.get("target").is_some_and(|t| !t.is_null()) {
|
||||
return Err("this build has no device targets (the engine's quantum_receipt feature)".into());
|
||||
}
|
||||
let (r, uses) = catalog::read_in(reg, request)?;
|
||||
// A method that runs a gate under many controls as one reads it so.
|
||||
let (r, uses) = catalog::read_form(reg, request, catalog::runs_whole(&plan.method, request))?;
|
||||
passes.clone_from(&r.compile_passes);
|
||||
let started = Started::now();
|
||||
let n = r.program.n_qubits;
|
||||
|
|
@ -253,6 +299,13 @@ fn run_job(reg: &Registry, request: &Value, plan: &Plan, passes: &mut Option<Val
|
|||
"qubits": n,
|
||||
"gates": r.program.ops.len(),
|
||||
});
|
||||
if r.program.has_whole() {
|
||||
// `gates` counts standard gates whichever way the method ran them, as it
|
||||
// always has (a receipt's provenance carries it); the operations it ran
|
||||
// are beside it. Within what JSON carries exactly.
|
||||
out["gates"] = json!(r.program.standard_gates().min((1u128 << 53) - 1) as u64);
|
||||
out["operations"] = json!(r.program.ops.len());
|
||||
}
|
||||
if let Some(c) = &r.compiled {
|
||||
out["compiled"] = c.clone();
|
||||
}
|
||||
|
|
@ -316,9 +369,9 @@ pub(crate) fn run_builtin(method: &str, job: &Job<'_>, plan: &Plan, out: &mut Va
|
|||
let n = r.program.n_qubits;
|
||||
match method {
|
||||
"exact" => {
|
||||
let c = r.program.dyadic()?;
|
||||
let (c, report) = r.program.dyadic_with_report()?;
|
||||
let sv = c.simulate().map_err(|e| e.to_string())?;
|
||||
out["accuracy"] = json!({ "class": "bit-exact" });
|
||||
out["accuracy"] = exact_accuracy(&report);
|
||||
out["state_hash"] = json!(hex(&sv.statevector_hash()));
|
||||
if r.shots > 0 {
|
||||
let per_index = sv.sample_shots(r.seed, r.shots);
|
||||
|
|
@ -380,11 +433,18 @@ pub(crate) fn run_builtin(method: &str, job: &Job<'_>, plan: &Plan, out: &mut Va
|
|||
}
|
||||
}
|
||||
"statevector" => {
|
||||
let gates = r.program.matrices();
|
||||
let width = r.options.get("fusion").and_then(Value::as_u64).unwrap_or(3).clamp(1, 5) as usize;
|
||||
// On the CPU, or on the fabric's member the job's placement
|
||||
// names: the CPU's bits either way.
|
||||
let (sv, placed) = crate::engine::fabric::statevector(request, n, &gates, width, crate::engine::fabric::widest_of(r))?;
|
||||
// names: the CPU's bits either way. A gate held whole runs as one,
|
||||
// on the CPU (catalog::may_run_whole).
|
||||
let (sv, placed) = if r.program.has_whole() {
|
||||
let mut sv = crate::quantum_sv::StateVector::zero(n).map_err(|e| format!("{e:?}"))?;
|
||||
sv.run_ops(&r.program.sv_ops(), width).map_err(|e| format!("{e:?}"))?;
|
||||
(sv, None)
|
||||
} else {
|
||||
let gates = r.program.matrices();
|
||||
crate::engine::fabric::statevector(request, n, &gates, width, crate::engine::fabric::widest_of(r))?
|
||||
};
|
||||
out["accuracy"] = json!({ "class": "exact", "fusion": width });
|
||||
if let Some(f) = placed {
|
||||
out["fabric"] = f;
|
||||
|
|
@ -556,8 +616,17 @@ pub(crate) fn run_builtin(method: &str, job: &Job<'_>, plan: &Plan, out: &mut Va
|
|||
use crate::quantum_mpsim::{Chain, MpsConfig};
|
||||
let chi = catalog::mps_bond(r);
|
||||
let cutoff = r.options.get("cutoff").and_then(Value::as_f64).unwrap_or(1e-12).clamp(0.0, 1e-2);
|
||||
let mut chain = Chain::run(n, &r.program.local_matrices(), MpsConfig { max_bond: chi, cutoff }).map_err(|e| e.to_string())?;
|
||||
let gates = r.program.local_matrices();
|
||||
// The plan's work bounds this run from above at the bond asked for;
|
||||
// the run is held to the bond it reaches.
|
||||
let mut chain = Chain::run_within(n, &gates, MpsConfig { max_bond: chi, cutoff }, mps_budget().saturating_sub(catalog::mps_shots_work(r, 1))).map_err(|e| match e {
|
||||
crate::quantum_mpsim::MpsRunError::Chain(e) => e.to_string(),
|
||||
crate::quantum_mpsim::MpsRunError::OutOfWork { gate, gates, work, max_bond_used } => crate::engine::refusal::tag(crate::engine::refusal::METHOD_REFUSES, format!("stopped in gate {gate} of {gates}: {work} units of work at the bond the circuit has reached ({max_bond_used}), the most one job may take ({}); ask for a smaller max_bond", mps_budget())),
|
||||
})?;
|
||||
let rep = chain.report();
|
||||
if chain.work().saturating_add(catalog::mps_shots_work(r, rep.max_bond_used as u64)) > mps_budget() {
|
||||
return Err(crate::engine::refusal::tag(crate::engine::refusal::METHOD_REFUSES, format!("{} shots at the bond the circuit reached ({}) are more work than one job has left; ask for fewer shots", r.shots, rep.max_bond_used)));
|
||||
}
|
||||
out["accuracy"] = if rep.truncations == 0 {
|
||||
json!({ "class": "exact", "max_bond": chi, "max_bond_used": rep.max_bond_used, "note": "no truncation dropped weight beyond the cutoff's rounding" })
|
||||
} else {
|
||||
|
|
@ -854,10 +923,19 @@ fn carried(r: &Request, plan: &Plan, out: &mut Value) {
|
|||
/// ([`crate::engine::dynamic`]): counts by shot splitting, expectation values as the
|
||||
/// exact mixture over its branches.
|
||||
fn run_dynamic(r: &catalog::Request, plan: &Plan, steps: &[crate::qasm::Instr], out: &mut Value) -> Result<(), String> {
|
||||
use crate::engine::dynamic::{self, Dense, Stabilizer};
|
||||
use crate::engine::dynamic::{self, Dense, DenseOps, Stabilizer};
|
||||
let (n, nb) = (r.program.n_qubits, r.program.n_bits);
|
||||
let strings: Vec<Vec<(u32, char)>> = r.observables.iter().map(|(_, _, s)| s.clone()).collect();
|
||||
let (sampled, exact) = match plan.method.as_str() {
|
||||
// A gate held whole runs as one between the measurements too.
|
||||
"statevector" if r.program.has_whole() => {
|
||||
let gates = r.program.sv_ops();
|
||||
let states = (plan.memory_bytes.saturating_sub((256 << 20) + r.shots * 24) / catalog::amplitude_bytes(n)).max(1) as usize;
|
||||
let start = DenseOps { sv: crate::quantum_sv::StateVector::zero(n).map_err(|e| format!("{e:?}"))?, ops: &gates };
|
||||
let s = if r.shots > 0 { Some(dynamic::sample(steps, nb, start.clone(), r.shots, r.seed, states)?) } else { None };
|
||||
let e = if strings.is_empty() { None } else { Some(dynamic::expectations(steps, &r.program.ops, nb, start, &strings, states)?) };
|
||||
(s, e)
|
||||
}
|
||||
"statevector" => {
|
||||
let gates = r.program.matrices();
|
||||
// The states the plan admitted: its memory less the job's margin and the counts.
|
||||
|
|
|
|||
|
|
@ -701,6 +701,10 @@ mod tests {
|
|||
let mut methods = std::collections::BTreeSet::new();
|
||||
for req in [
|
||||
json!({ "qasm": bell, "method": "exact", "shots": 100, "observables": ["Z0 Z1", { "pauli": "X0 X1", "coeff": 0.5 }], "amplitudes": ["00", "11"] }),
|
||||
// A gate held whole, the phase the method drops, an angle below its table and one it moved.
|
||||
json!({ "qasm": "OPENQASM 3.0;\nqubit[4] q;\nh q;\nctrl(3) @ z q[0], q[1], q[2], q[3];\nrz(pi / 4) q[0];\np(2 * pi / 2**50) q[1];\np(pi / 2 + 2 * pi / 2**48) q[2];\n", "method": "exact", "shots": 8 }),
|
||||
json!({ "qasm": "OPENQASM 3.0;\nqubit[4] q;\nh q;\nctrl(3) @ z q[0], q[1], q[2], q[3];\nrz(pi / 4) q[0];\n", "method": "exact", "shots": 8, "options": { "controls": "whole" } }),
|
||||
json!({ "qasm": "OPENQASM 3.0;\nqubit[4] q;\nh q;\nctrl(3) @ x q[0], q[1], q[2], q[3];\n", "method": "statevector", "shots": 8 }),
|
||||
json!({ "qasm": rot, "method": "statevector", "shots": 100, "observables": ["Z0"], "amplitudes": ["000"], "seed": 3 }),
|
||||
json!({ "qasm": bell, "shots": 100, "observables": ["Z0 Z1"] }),
|
||||
json!({ "qasm": bell, "method": "stabilizer", "shots": 100, "observables": ["Z0 Z1"], "noise": { "depolarizing_1q": 0.01, "readout": 0.02 } }),
|
||||
|
|
|
|||
|
|
@ -236,6 +236,7 @@ pub fn summary(plans: &[catalog::Plan]) -> catalog::Plan {
|
|||
work: plans.iter().fold(0u64, |a, p| a.saturating_add(p.work)),
|
||||
qubits: plans.iter().map(|p| p.qubits).max().unwrap_or(0),
|
||||
target: None,
|
||||
metered: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ pub fn plan(v: &Value, job_bytes: u64) -> Result<Plan, String> {
|
|||
if !pl.native.exact {
|
||||
why.push_str("; each gate costed as the target's own, equal up to one-qubit gates");
|
||||
}
|
||||
Ok(Plan { method, why, memory_bytes, work, qubits: p.noise.physical.len() as u32, target: Some(target_json(&p)) })
|
||||
Ok(Plan { method, why, memory_bytes, work, qubits: p.noise.physical.len() as u32, target: Some(target_json(&p)), metered: false })
|
||||
}
|
||||
|
||||
/// The bit string a simulated basis state reads as: logical qubit `i` read
|
||||
|
|
|
|||
|
|
@ -538,7 +538,9 @@ fn distribution(o: &Map<String, Value>, p: &Program, noise: Option<&crate::quant
|
|||
/// What the reader said of `src`: `{"ok": true, "diagnostics": […]}`, or
|
||||
/// `{"ok": false}` with its refusal as a diagnostic's JSON; and the program.
|
||||
fn parsed(src: &str) -> (Value, Option<qasm::Reading>) {
|
||||
match qasm::read(src) {
|
||||
// A gate past what standard gates hold still reads: a job can hold it whole.
|
||||
let read = qasm::read(src).or_else(|d| if d.code == qasm::diag::TOO_MANY_CONTROLS || d.code == qasm::diag::TOO_MANY_GATES { qasm::read_whole(src) } else { Err(d) });
|
||||
match read {
|
||||
Ok(r) => (json!({ "ok": true, "diagnostics": r.diagnostics.iter().map(|d| d.to_json()).collect::<Vec<_>>() }), Some(r)),
|
||||
Err(d) => {
|
||||
let mut v = d.to_json();
|
||||
|
|
|
|||
|
|
@ -37,6 +37,13 @@
|
|||
//! the tests hold each lowering to the others. The bit-exact method takes the
|
||||
//! reader's program under its own restriction, dyadic angles
|
||||
//! ([`dyadic_circuit`], [`exact_circuit`]), refused with a code otherwise.
|
||||
//!
|
||||
//! **Gates under many controls.** [`read`] writes every gate in standard gates,
|
||||
//! which for a gate under many controls doubles with each control.
|
||||
//! [`read_whole`] keeps such a gate as one op ([`Op::is_whole`]), for the methods
|
||||
//! that run it so: the dense state vector ([`Program::sv_ops`]) and the bit-exact
|
||||
//! one ([`Program::dyadic`]). [`Program::lowered`] writes a whole program in
|
||||
//! standard gates: what [`read`] gives of the same text, op for op.
|
||||
|
||||
pub mod diag;
|
||||
pub mod lex;
|
||||
|
|
@ -44,6 +51,7 @@ pub mod ast;
|
|||
pub mod parse;
|
||||
pub mod value;
|
||||
pub(crate) mod elab;
|
||||
mod controls;
|
||||
mod unroll;
|
||||
/// OpenPulse: the calibrations read into a schedule on frames, for the pulse
|
||||
/// simulator (`quantum_pulse`).
|
||||
|
|
@ -180,6 +188,11 @@ fn normalise(kind: Kind, given: usize, args: Vec<f64>) -> Vec<f64> {
|
|||
pub type CliffordOp = (Vec<Clifford>, Vec<u32>);
|
||||
|
||||
/// One gate on named qubits.
|
||||
///
|
||||
/// In a program [`read_whole`] gave, `qubits` may hold more qubits than the
|
||||
/// kind's arity: the gate, a one-qubit one, under controls, the controls first
|
||||
/// and the target last ([`Op::is_whole`]). [`read`] and [`parse`] never give
|
||||
/// one.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Op {
|
||||
pub kind: Kind,
|
||||
|
|
@ -187,6 +200,19 @@ pub struct Op {
|
|||
pub params: Vec<f64>,
|
||||
}
|
||||
|
||||
impl Op {
|
||||
/// Whether this is one gate under controls that the standard form writes
|
||||
/// as many ([`read_whole`]).
|
||||
pub fn is_whole(&self) -> bool {
|
||||
self.qubits.len() > self.kind.arity().1
|
||||
}
|
||||
|
||||
/// The controls of a whole gate, and none of any other.
|
||||
pub fn controls(&self) -> &[u32] {
|
||||
&self.qubits[..self.qubits.len().saturating_sub(self.kind.arity().1)]
|
||||
}
|
||||
}
|
||||
|
||||
/// Which qubit each bit holds once a program has run, for a program whose
|
||||
/// measurements all come at the end: a bit written twice holds the qubit it
|
||||
/// was written from last. Measurements at the end act on separate qubits and
|
||||
|
|
@ -374,6 +400,22 @@ pub fn read_with(src: &str, inputs: &Inputs) -> Result<Reading, Diagnostic> {
|
|||
Ok(Reading { program, lines, diagnostics })
|
||||
}
|
||||
|
||||
/// [`read`], each gate whose standard form would double with its controls
|
||||
/// kept as one op ([`Op::is_whole`]), under up to 255 controls: for a method
|
||||
/// that runs such a gate as one. Everything else reads as [`read`] reads it,
|
||||
/// and [`Program::lowered`] gives the program [`read`] gives.
|
||||
pub fn read_whole(src: &str) -> Result<Reading, Diagnostic> {
|
||||
read_whole_with(src, &Inputs::new())
|
||||
}
|
||||
|
||||
/// [`read_whole`] for a generator, at `inputs`.
|
||||
pub fn read_whole_with(src: &str, inputs: &Inputs) -> Result<Reading, Diagnostic> {
|
||||
let toks = lex::lex(src)?;
|
||||
let (stmts, version) = parse::parse(src, &toks)?;
|
||||
let (program, lines, diagnostics) = elab::elaborate_whole(src, &toks, &stmts, version, inputs)?;
|
||||
Ok(Reading { program, lines, diagnostics })
|
||||
}
|
||||
|
||||
/// The integer `input`s a program declares (`input uint n;`, `input int k;`),
|
||||
/// in the order it declares them. A program that declares one is a
|
||||
/// generator: [`parse_with`] reads it for each value, and [`parse`] refuses it,
|
||||
|
|
@ -664,6 +706,11 @@ impl Op {
|
|||
use Kind::*;
|
||||
let q = self.qubits.clone();
|
||||
let p = &self.params;
|
||||
if self.is_whole() {
|
||||
let c = self.controls().len() as u32;
|
||||
assert!(c <= 12, "{c} controls: a whole gate's dense matrix has 4^{} entries; run it through Program::sv_ops, or write it in standard gates (Program::lowered)", c + 1);
|
||||
return Matrix { qubits: q, matrix: controlled(&one_qubit(self.kind, p), c) };
|
||||
}
|
||||
let matrix = match self.kind {
|
||||
Id | X | Y | Z | H | S | Sdg | T | Tdg | Sx | Sxdg | Rx | Ry | Rz | P | U => one_qubit(self.kind, p).to_vec(),
|
||||
Cx => controlled(&one_qubit(X, &[]), 1),
|
||||
|
|
@ -747,8 +794,72 @@ pub(crate) fn dyadic(angle: f64) -> Option<u64> {
|
|||
None
|
||||
}
|
||||
|
||||
/// What lowering a program to the bit-exact circuit did to it, beside the
|
||||
/// gates: what a result on that method states ([`Program::dyadic_with_report`]).
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct DyadicReport {
|
||||
/// The global phase the lowering drops, in `2^33`rds of a turn: the state
|
||||
/// method statevector gives of the same program is `e^{2πi·g/2^33}` times
|
||||
/// the circuit's. `rz`, `rx`, `ry`, `rxx`, `ryy`, `rzz` and `u` become phase
|
||||
/// gates, each `e^{iθ/2}` from the matrix that method applies; a gate under
|
||||
/// a control keeps its phase. That is not the language's own state where
|
||||
/// the two differ: its `U` is `e^{iθ/2}` times the matrix both methods take
|
||||
/// as `u`, and the reader drops an uncontrolled `gphase` on every method.
|
||||
pub global_phase: u64,
|
||||
/// Phase gates of the lowering (one angle written may be several) that are
|
||||
/// not zero and ran as no phase: within the reader's tolerance, 1e-14 of a
|
||||
/// turn, of a whole turn, below every entry of the table.
|
||||
pub below_table: u64,
|
||||
/// The largest of them, in radians from a whole turn.
|
||||
pub largest_below_table: f64,
|
||||
/// Phase gates whose angle moved to the dyadic one within the reader's
|
||||
/// tolerance by more than a double's rounding of it.
|
||||
pub moved: u64,
|
||||
/// The largest such move, in radians.
|
||||
pub largest_moved: f64,
|
||||
}
|
||||
|
||||
impl DyadicReport {
|
||||
/// The global phase in radians, in `(−π, π]`.
|
||||
pub fn global_phase_radians(&self) -> f64 {
|
||||
const FULL: u64 = 1 << 33;
|
||||
let signed = if self.global_phase > FULL / 2 { self.global_phase as f64 - FULL as f64 } else { self.global_phase as f64 };
|
||||
signed / FULL as f64 * std::f64::consts::TAU
|
||||
}
|
||||
|
||||
/// `e^{iθ/2}` dropped: the phase the standard's matrix has beyond the
|
||||
/// lowered gate's.
|
||||
fn drop_half(&mut self, theta: f64) {
|
||||
const FULL: u64 = 1 << 33;
|
||||
// θ in 2^32nds of a turn, whole turns kept: rz(2π) is −1.
|
||||
let m = (theta / std::f64::consts::TAU * (1u64 << 32) as f64).round();
|
||||
let m = (m as i128).rem_euclid(FULL as i128) as u64;
|
||||
self.global_phase = (self.global_phase + FULL - m) % FULL;
|
||||
}
|
||||
|
||||
/// What reading `angle` as the dyadic `m` (in `2^32`nds of a turn) moved.
|
||||
fn read(&mut self, angle: f64, m: u64) {
|
||||
let t = (angle / std::f64::consts::TAU).rem_euclid(1.0);
|
||||
let at = m as f64 / (1u64 << 32) as f64;
|
||||
let gap = (t - at).abs().min(1.0 - (t - at).abs());
|
||||
// A double's rounding of the dyadic angle moves nothing.
|
||||
if gap <= 16.0 * f64::EPSILON * (angle.abs() / std::f64::consts::TAU) {
|
||||
return;
|
||||
}
|
||||
let radians = gap * std::f64::consts::TAU;
|
||||
if m == 0 {
|
||||
self.below_table += 1;
|
||||
self.largest_below_table = self.largest_below_table.max(radians);
|
||||
} else {
|
||||
self.moved += 1;
|
||||
self.largest_moved = self.largest_moved.max(radians);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DyadicOut {
|
||||
c: Dyadic,
|
||||
report: DyadicReport,
|
||||
}
|
||||
|
||||
impl DyadicOut {
|
||||
|
|
@ -759,6 +870,7 @@ impl DyadicOut {
|
|||
/// `diag(1, e^{iλ})` on `target` under `controls`: one `P(k)` per set bit.
|
||||
fn phase(&mut self, controls: &[u32], target: u32, lambda: f64) -> Result<(), Diagnostic> {
|
||||
let m = dyadic(lambda).ok_or_else(|| Diagnostic::new(diag::NOT_DYADIC, None, format!("the angle {lambda} is not a dyadic multiple of 2π (2πm/2^k, k ≤ 32)")))?;
|
||||
self.report.read(lambda, m);
|
||||
for j in 0..32u16 {
|
||||
if m >> j & 1 == 1 {
|
||||
self.g(BaseGate::P, controls, target, 32 - j);
|
||||
|
|
@ -797,12 +909,76 @@ impl DyadicOut {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// `rz(θ)` on `t` under `cs` (at least one): `p(θ)` under them, and the
|
||||
/// `e^{−iθ/2}` a control keeps, on the last of them under the others.
|
||||
fn rz_under(&mut self, cs: &[u32], t: u32, theta: f64) -> Result<(), Diagnostic> {
|
||||
self.phase(cs, t, theta)?;
|
||||
let (last, rest) = cs.split_last().expect("a whole gate has controls");
|
||||
self.phase(rest, *last, -theta / 2.0)
|
||||
}
|
||||
|
||||
/// A one-qubit gate under its controls, every gate of it under them all,
|
||||
/// so an amplitude a control turns off is not touched.
|
||||
fn whole(&mut self, op: &Op) -> Result<(), Diagnostic> {
|
||||
use Kind::*;
|
||||
let (cs, t, p) = (op.controls(), op.qubits[op.qubits.len() - 1], &op.params);
|
||||
match op.kind {
|
||||
X => self.g(BaseGate::X, cs, t, 0),
|
||||
Y => self.g(BaseGate::Y, cs, t, 0),
|
||||
Z => self.g(BaseGate::Z, cs, t, 0),
|
||||
H => self.g(BaseGate::H, cs, t, 0),
|
||||
S => self.g(BaseGate::S, cs, t, 0),
|
||||
Sdg => self.g(BaseGate::Sdg, cs, t, 0),
|
||||
T => self.g(BaseGate::T, cs, t, 0),
|
||||
Tdg => self.g(BaseGate::Tdg, cs, t, 0),
|
||||
P => self.phase(cs, t, p[0])?,
|
||||
Sx | Sxdg => {
|
||||
self.g(BaseGate::H, cs, t, 0);
|
||||
self.g(if op.kind == Sx { BaseGate::S } else { BaseGate::Sdg }, cs, t, 0);
|
||||
self.g(BaseGate::H, cs, t, 0);
|
||||
}
|
||||
Rz => self.rz_under(cs, t, p[0])?,
|
||||
Rx => {
|
||||
self.g(BaseGate::H, cs, t, 0);
|
||||
self.rz_under(cs, t, p[0])?;
|
||||
self.g(BaseGate::H, cs, t, 0);
|
||||
}
|
||||
Ry => {
|
||||
self.g(BaseGate::Sdg, cs, t, 0);
|
||||
self.g(BaseGate::H, cs, t, 0);
|
||||
self.rz_under(cs, t, p[0])?;
|
||||
self.g(BaseGate::H, cs, t, 0);
|
||||
self.g(BaseGate::S, cs, t, 0);
|
||||
}
|
||||
// U(θ, φ, λ) = p(φ) ry(θ) p(λ).
|
||||
U => {
|
||||
self.phase(cs, t, p[2])?;
|
||||
self.g(BaseGate::Sdg, cs, t, 0);
|
||||
self.g(BaseGate::H, cs, t, 0);
|
||||
self.rz_under(cs, t, p[0])?;
|
||||
self.g(BaseGate::H, cs, t, 0);
|
||||
self.g(BaseGate::S, cs, t, 0);
|
||||
self.phase(cs, t, p[1])?;
|
||||
}
|
||||
_ => unreachable!("only a one-qubit gate stands whole"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// One op in the dyadic gate set.
|
||||
fn op(&mut self, op: &Op) -> Result<(), Diagnostic> {
|
||||
if op.is_whole() {
|
||||
return self.whole(op);
|
||||
}
|
||||
let o = self;
|
||||
let q = &op.qubits;
|
||||
let p = &op.params;
|
||||
use Kind::*;
|
||||
match op.kind {
|
||||
Rx | Ry | Rz | Rzz | Rxx | Ryy => o.report.drop_half(p[0]),
|
||||
U => o.report.drop_half(p[0]),
|
||||
_ => {}
|
||||
}
|
||||
match op.kind {
|
||||
Id => {}
|
||||
X => o.g(BaseGate::X, &[], q[0], 0),
|
||||
|
|
@ -1086,50 +1262,85 @@ impl Program {
|
|||
/// tensor networks and the noisy simulators (qubit 0 the most significant
|
||||
/// bit of their index).
|
||||
#[cfg(feature = "quantum_qasm")]
|
||||
///
|
||||
/// A gate held whole is written in standard gates first; past what those
|
||||
/// take this panics, so a caller holding a whole program checks
|
||||
/// [`Program::lowered`] first, or takes [`Program::sv_ops`].
|
||||
pub fn matrices(&self) -> Vec<Matrix> {
|
||||
if self.has_whole() {
|
||||
return self.standard().matrices();
|
||||
}
|
||||
self.ops.iter().map(Op::matrix).collect()
|
||||
}
|
||||
|
||||
/// The program in standard gates, for a lowering that takes no other.
|
||||
#[cfg(feature = "quantum_qasm")]
|
||||
fn standard(&self) -> Program {
|
||||
self.lowered().unwrap_or_else(|d| panic!("{d}"))
|
||||
}
|
||||
|
||||
/// Every gate as a matrix on at most two qubits: the input of the matrix
|
||||
/// product state. The three-qubit gates are decomposed exactly (Toffoli
|
||||
/// into CX and T gates, as qelib1.inc writes it).
|
||||
#[cfg(feature = "quantum_qasm")]
|
||||
pub fn local_matrices(&self) -> Vec<Matrix> {
|
||||
let op = |kind: Kind, qubits: &[u32]| Op { kind, qubits: qubits.to_vec(), params: vec![] }.matrix();
|
||||
let ccx = |out: &mut Vec<Matrix>, a: u32, b: u32, c: u32| {
|
||||
use Kind::*;
|
||||
for (k, q) in [
|
||||
(H, vec![c]), (Cx, vec![b, c]), (Tdg, vec![c]), (Cx, vec![a, c]), (T, vec![c]), (Cx, vec![b, c]), (Tdg, vec![c]), (Cx, vec![a, c]),
|
||||
(T, vec![b]), (T, vec![c]), (H, vec![c]), (Cx, vec![a, b]), (T, vec![a]), (Tdg, vec![b]), (Cx, vec![a, b]),
|
||||
] {
|
||||
out.push(op(k, &q));
|
||||
if self.has_whole() {
|
||||
return self.standard().local_matrices();
|
||||
}
|
||||
let mut out = Vec::with_capacity(self.ops.len());
|
||||
self.local_steps(|kind, qubits, params| out.push(Op { kind, qubits: qubits.to_vec(), params: params.to_vec() }.matrix()));
|
||||
out
|
||||
}
|
||||
|
||||
/// The gates [`local_matrices`](Program::local_matrices) writes, in order,
|
||||
/// each given to `each` as its kind, qubits and parameters, without
|
||||
/// writing any: what the matrix product state's work is planned on. Of a
|
||||
/// program in standard gates (one that holds a gate whole is walked as
|
||||
/// read).
|
||||
#[cfg(feature = "quantum_qasm")]
|
||||
pub fn local_steps(&self, mut each: impl FnMut(Kind, &[u32], &[f64])) {
|
||||
use Kind::*;
|
||||
// Toffoli on (a, b, c), its gates on [a, b, c][i].
|
||||
const CCX: [(Kind, &[usize]); 15] = [
|
||||
(H, &[2]), (Cx, &[1, 2]), (Tdg, &[2]), (Cx, &[0, 2]), (T, &[2]), (Cx, &[1, 2]), (Tdg, &[2]), (Cx, &[0, 2]),
|
||||
(T, &[1]), (T, &[2]), (H, &[2]), (Cx, &[0, 1]), (T, &[0]), (Tdg, &[1]), (Cx, &[0, 1]),
|
||||
];
|
||||
type Step<'a> = dyn FnMut(Kind, &[u32], &[f64]) + 'a;
|
||||
let ccx = |each: &mut Step<'_>, abc: [u32; 3]| {
|
||||
let mut q = [0u32; 2];
|
||||
for (k, at) in CCX {
|
||||
for (j, &i) in at.iter().enumerate() {
|
||||
q[j] = abc[i];
|
||||
}
|
||||
each(k, &q[..at.len()], &[]);
|
||||
}
|
||||
};
|
||||
let mut out = Vec::with_capacity(self.ops.len());
|
||||
for o in &self.ops {
|
||||
let q = &o.qubits;
|
||||
match o.kind {
|
||||
Kind::Ccx => ccx(&mut out, q[0], q[1], q[2]),
|
||||
Kind::Ccz => {
|
||||
out.push(op(Kind::H, &[q[2]]));
|
||||
ccx(&mut out, q[0], q[1], q[2]);
|
||||
out.push(op(Kind::H, &[q[2]]));
|
||||
Ccx => ccx(&mut each, [q[0], q[1], q[2]]),
|
||||
Ccz => {
|
||||
each(H, &[q[2]], &[]);
|
||||
ccx(&mut each, [q[0], q[1], q[2]]);
|
||||
each(H, &[q[2]], &[]);
|
||||
}
|
||||
Kind::Cswap => {
|
||||
out.push(op(Kind::Cx, &[q[2], q[1]]));
|
||||
ccx(&mut out, q[0], q[1], q[2]);
|
||||
out.push(op(Kind::Cx, &[q[2], q[1]]));
|
||||
Cswap => {
|
||||
each(Cx, &[q[2], q[1]], &[]);
|
||||
ccx(&mut each, [q[0], q[1], q[2]]);
|
||||
each(Cx, &[q[2], q[1]], &[]);
|
||||
}
|
||||
_ => out.push(o.matrix()),
|
||||
_ => each(o.kind, q, &o.params),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The circuit as Pauli rotations and Cliffords: the input of sparse Pauli
|
||||
/// dynamics. Exact up to a global phase, which no expectation value sees.
|
||||
#[cfg(feature = "quantum_qasm")]
|
||||
pub fn rotations(&self) -> RotCircuit {
|
||||
if self.has_whole() {
|
||||
return self.standard().rotations();
|
||||
}
|
||||
let mut out = RotCircuit::new(self.n_qubits);
|
||||
for op in &self.ops {
|
||||
rot(&mut out, op);
|
||||
|
|
@ -1138,9 +1349,11 @@ impl Program {
|
|||
}
|
||||
|
||||
/// The circuit in the dyadic gate set: the input of the bit-exact state
|
||||
/// vector and the stabilizer tableau. Exact up to a global phase; refused
|
||||
/// when an angle is not `2πm/2^k` (`k ≤ 32`), or the program is wider than
|
||||
/// the dyadic circuit holds (255 qubits).
|
||||
/// vector and the stabilizer tableau. Exact up to a global phase
|
||||
/// ([`Program::dyadic_with_report`] says which); refused when an angle is
|
||||
/// not `2πm/2^k` (`k ≤ 32`), or the program is wider than the dyadic circuit
|
||||
/// holds (255 qubits). A gate held whole ([`Op::is_whole`]) is its gates
|
||||
/// under all its controls, with no global phase.
|
||||
pub fn dyadic(&self) -> Result<Dyadic, String> {
|
||||
self.lower_dyadic(false).map_err(String::from)
|
||||
}
|
||||
|
|
@ -1152,11 +1365,158 @@ impl Program {
|
|||
self.lower_dyadic(true)
|
||||
}
|
||||
|
||||
/// [`Program::dyadic`], with what the lowering did beside the gates: the
|
||||
/// global phase it dropped and the angles it read as another
|
||||
/// ([`DyadicReport`]).
|
||||
pub fn dyadic_with_report(&self) -> Result<(Dyadic, DyadicReport), String> {
|
||||
self.lower_dyadic_reported(false).map_err(String::from)
|
||||
}
|
||||
|
||||
/// Whether any op is a gate held whole under its controls.
|
||||
pub fn has_whole(&self) -> bool {
|
||||
self.ops.iter().any(Op::is_whole)
|
||||
}
|
||||
|
||||
/// How many standard gates the program is written in, without writing
|
||||
/// them: its ops' count for a program with none held whole.
|
||||
pub fn standard_gates(&self) -> u128 {
|
||||
Self::standard_count(&self.ops)
|
||||
}
|
||||
|
||||
/// How many steps a dynamic program is written in in standard gates, without
|
||||
/// writing them; `None` for a program with no steps.
|
||||
pub fn standard_steps(&self) -> Option<u128> {
|
||||
let len = |o: &Op| if o.is_whole() { controls::whole_len(o.kind, &o.params, o.controls().len()) } else { 1 };
|
||||
self.dynamic.as_ref().map(|steps| steps.iter().fold(0u128, |n, s| n.saturating_add(if let Instr::Gate(i) = s { len(&self.ops[*i]) } else { 1 })))
|
||||
}
|
||||
|
||||
fn standard_count(ops: &[Op]) -> u128 {
|
||||
ops.iter().fold(0u128, |n, o| n.saturating_add(if o.is_whole() { controls::whole_len(o.kind, &o.params, o.controls().len()) } else { 1 }))
|
||||
}
|
||||
|
||||
/// The program in standard gates: of a program [`read_whole`] gave, the
|
||||
/// program [`read`] gives of the same text, op for op, its steps, timing
|
||||
/// and records moved with them. Refused where [`read`] refuses it: more
|
||||
/// controls on one gate, or more gates, than the standard form takes
|
||||
/// ([`MAX_OPS`]).
|
||||
pub fn lowered(&self) -> Result<Program, Diagnostic> {
|
||||
self.lowered_lines(&[]).map(|(p, _)| p)
|
||||
}
|
||||
|
||||
/// How many standard gates `ops` are, refused, as `read` refuses them, when a
|
||||
/// gate has more controls than the standard form takes or they come to more
|
||||
/// than a program may hold: counted without writing them.
|
||||
fn standard_within(ops: &[Op]) -> Result<u128, Diagnostic> {
|
||||
if ops.iter().any(|o| o.controls().len() > elab::MAX_CONTROLS as usize) {
|
||||
return Err(Diagnostic::new(diag::TOO_MANY_CONTROLS, None, format!("written in standard gates, modifiers add at most {} controls; {}", elab::MAX_CONTROLS, elab::HELD_WHOLE)));
|
||||
}
|
||||
let total = Self::standard_count(ops);
|
||||
if total > MAX_OPS as u128 {
|
||||
return Err(Diagnostic::new(diag::TOO_MANY_GATES, None, format!("{total} gates written in standard gates; more than {MAX_OPS}")));
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
/// The program in standard gates, written only if it holds a gate whole:
|
||||
/// what a consumer of standard gates reads a program through.
|
||||
pub fn standard_form(&self) -> Result<std::borrow::Cow<'_, Program>, Diagnostic> {
|
||||
if self.has_whole() { self.lowered().map(std::borrow::Cow::Owned) } else { Ok(std::borrow::Cow::Borrowed(self)) }
|
||||
}
|
||||
|
||||
/// [`Program::lowered`] with each op's line (`lines` as a [`Reading`] holds
|
||||
/// them, or empty for none).
|
||||
pub fn lowered_lines(&self, lines: &[usize]) -> Result<(Program, Vec<usize>), Diagnostic> {
|
||||
if !self.has_whole() {
|
||||
return Ok((self.clone(), lines.to_vec()));
|
||||
}
|
||||
let total = Self::standard_within(&self.ops)?;
|
||||
struct Standard {
|
||||
ops: Vec<Op>,
|
||||
}
|
||||
impl controls::Sink for Standard {
|
||||
type Error = std::convert::Infallible;
|
||||
fn gate(&mut self, kind: Kind, params: Vec<f64>, qubits: Vec<u32>) -> Result<(), Self::Error> {
|
||||
self.ops.push(Op { kind, qubits, params });
|
||||
Ok(())
|
||||
}
|
||||
fn whole(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
let mut out = Standard { ops: Vec::with_capacity(total as usize) };
|
||||
// Where each op's gates start.
|
||||
let mut at = Vec::with_capacity(self.ops.len() + 1);
|
||||
let mut new_lines = Vec::with_capacity(if lines.is_empty() { 0 } else { total as usize });
|
||||
for (i, op) in self.ops.iter().enumerate() {
|
||||
at.push(out.ops.len());
|
||||
if op.is_whole() {
|
||||
let (t, cs) = op.qubits.split_last().expect("a whole gate has qubits");
|
||||
let Ok(_) = controls::ctrl_one(&mut out, op.kind, &op.params, cs, *t);
|
||||
} else {
|
||||
out.ops.push(op.clone());
|
||||
}
|
||||
if let Some(&l) = lines.get(i) {
|
||||
new_lines.resize(out.ops.len(), l);
|
||||
}
|
||||
}
|
||||
at.push(out.ops.len());
|
||||
let dynamic = self.dynamic.as_ref().map(|steps| {
|
||||
// Where each step lands, one past the end included: a jump may go there.
|
||||
let mut step_at = Vec::with_capacity(steps.len() + 1);
|
||||
let mut next = 0;
|
||||
for s in steps {
|
||||
step_at.push(next);
|
||||
next += if let Instr::Gate(i) = s { at[*i + 1] - at[*i] } else { 1 };
|
||||
}
|
||||
step_at.push(next);
|
||||
let mut written = Vec::with_capacity(next);
|
||||
for s in steps {
|
||||
match s {
|
||||
Instr::Gate(i) => written.extend((at[*i]..at[*i + 1]).map(Instr::Gate)),
|
||||
Instr::JumpUnless(c, to) => written.push(Instr::JumpUnless(c.clone(), step_at[*to])),
|
||||
Instr::Jump(to) => written.push(Instr::Jump(step_at[*to])),
|
||||
other => written.push(other.clone()),
|
||||
}
|
||||
}
|
||||
written
|
||||
});
|
||||
let mut extra = self.extra.clone();
|
||||
for t in &mut extra.timing {
|
||||
t.at = at[t.at];
|
||||
}
|
||||
for n in &mut extra.native {
|
||||
(n.first, n.end) = (at[n.first], at[n.end]);
|
||||
}
|
||||
Ok((Program { n_qubits: self.n_qubits, ops: out.ops, measured: self.measured.clone(), n_bits: self.n_bits, dynamic, extra }, new_lines))
|
||||
}
|
||||
|
||||
/// The gates a dense state vector runs: each gate held whole as one
|
||||
/// controlled gate, every other as its matrix.
|
||||
#[cfg(feature = "quantum_qasm")]
|
||||
pub fn sv_ops(&self) -> Vec<crate::quantum_sv::SvOp> {
|
||||
use crate::quantum_sv::{Controlled, SvOp};
|
||||
self.ops
|
||||
.iter()
|
||||
.map(|o| {
|
||||
if o.is_whole() {
|
||||
let (t, cs) = o.qubits.split_last().expect("a whole gate has qubits");
|
||||
SvOp::Controlled(Controlled { controls: cs.to_vec(), target: *t, matrix: one_qubit(o.kind, &o.params) })
|
||||
} else {
|
||||
SvOp::Gate(o.matrix())
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn lower_dyadic(&self, native: bool) -> Result<Dyadic, Diagnostic> {
|
||||
self.lower_dyadic_reported(native).map(|(c, _)| c)
|
||||
}
|
||||
|
||||
fn lower_dyadic_reported(&self, native: bool) -> Result<(Dyadic, DyadicReport), Diagnostic> {
|
||||
if self.n_qubits > 255 {
|
||||
return Err(Diagnostic::new(diag::DYADIC_WIDTH, None, format!("{} qubits; the dyadic circuit holds at most 255", self.n_qubits)));
|
||||
}
|
||||
let mut o = DyadicOut { c: Dyadic::new(self.n_qubits as u8) };
|
||||
let mut o = DyadicOut { c: Dyadic::new(self.n_qubits as u8), report: DyadicReport::default() };
|
||||
// The native forms hold only if they still stand where they were made.
|
||||
let n = &self.extra.native;
|
||||
let sound = native && n.iter().all(|x| x.first <= x.end && x.end <= self.ops.len()) && n.windows(2).all(|w| w[0].end <= w[1].first);
|
||||
|
|
@ -1174,7 +1534,7 @@ impl Program {
|
|||
o.op(&self.ops[i])?;
|
||||
i += 1;
|
||||
}
|
||||
Ok(o.c)
|
||||
Ok((o.c, o.report))
|
||||
}
|
||||
|
||||
/// The circuit as Clifford gates on the wide tableau, exact up to a global
|
||||
|
|
@ -1214,6 +1574,9 @@ impl Program {
|
|||
/// written as, and this is the product of the `e^{iθ}`.
|
||||
#[cfg(feature = "quantum_qasm")]
|
||||
pub fn product_measured(&self) -> Result<ProductMeasuredParts, String> {
|
||||
if self.has_whole() {
|
||||
return self.lowered().map_err(String::from)?.product_measured();
|
||||
}
|
||||
let one = complex(1.0, 0.0);
|
||||
let zero = complex(0.0, 0.0);
|
||||
let mul = |a: &[C], b: &[C; 4]| -> [C; 4] {
|
||||
|
|
@ -1261,6 +1624,9 @@ impl Program {
|
|||
/// not a whole number of sixteenths of a turn.
|
||||
#[cfg(feature = "quantum_qasm")]
|
||||
pub fn clifford_t(&self) -> Result<crate::quantum_stabrank::CliffordT, String> {
|
||||
if self.has_whole() {
|
||||
return self.lowered().map_err(String::from)?.clifford_t();
|
||||
}
|
||||
use crate::quantum_stabrank::{CliffordT, CtGate};
|
||||
use Kind::*;
|
||||
let mut gates = Vec::new();
|
||||
|
|
@ -1351,9 +1717,43 @@ impl Program {
|
|||
#[cfg(feature = "quantum_qasm")]
|
||||
pub(crate) fn lower_clifford(ops: &[Op]) -> Result<(Vec<Clifford>, Vec<usize>), String> {
|
||||
use Kind::*;
|
||||
// A gate held whole is checked in its standard gates, so it is held to
|
||||
// what `lowered` holds them to before any is written: past that, a few
|
||||
// bytes of text would ask for 2^(m+1) of them.
|
||||
if ops.iter().any(Op::is_whole) {
|
||||
Self::standard_within(ops).map_err(String::from)?;
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
let mut ends = Vec::with_capacity(ops.len());
|
||||
// A gate held whole is Clifford only as its standard gates are: they are
|
||||
// written one by one, and the first that is not ends it. Their phases
|
||||
// are read exactly: θ/2^(m−1) under m qubits is a quarter turn only when
|
||||
// it is one, and a phase smaller than the reader's tolerance there is no
|
||||
// zero, which `quarter` would take it for (every one of 2^m of them).
|
||||
struct Each<'a> {
|
||||
out: &'a mut Vec<Clifford>,
|
||||
}
|
||||
impl controls::Sink for Each<'_> {
|
||||
type Error = String;
|
||||
fn gate(&mut self, kind: Kind, params: Vec<f64>, qubits: Vec<u32>) -> Result<(), String> {
|
||||
if let Some(&p) = params.iter().find(|&&p| p != 0.0 && p.abs() < std::f64::consts::FRAC_PI_4) {
|
||||
return Err(format!("{kind:?}({p}) in a gate under many controls is not a Clifford gate"));
|
||||
}
|
||||
let (gates, _) = Program::lower_clifford(&[Op { kind, qubits, params }])?;
|
||||
self.out.extend(gates);
|
||||
Ok(())
|
||||
}
|
||||
fn whole(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
for op in ops {
|
||||
if op.is_whole() {
|
||||
let (t, cs) = op.qubits.split_last().expect("a whole gate has qubits");
|
||||
controls::ctrl_one(&mut Each { out: &mut out }, op.kind, &op.params, cs, *t)?;
|
||||
ends.push(out.len());
|
||||
continue;
|
||||
}
|
||||
let q = &op.qubits;
|
||||
let p = &op.params;
|
||||
let not = || format!("{:?}{} is not a Clifford gate", op.kind, if p.is_empty() { String::new() } else { format!("({})", p.iter().map(|x| format!("{x}")).collect::<Vec<_>>().join(", ")) });
|
||||
|
|
@ -1460,6 +1860,8 @@ impl Program {
|
|||
|
||||
#[cfg(all(test, feature = "quantum_qasm"))]
|
||||
mod lang_tests;
|
||||
#[cfg(all(test, feature = "quantum_qasm"))]
|
||||
mod whole_tests;
|
||||
|
||||
#[cfg(all(test, feature = "quantum_qasm"))]
|
||||
mod tests {
|
||||
|
|
|
|||
318
wai/wai-quantum/src/qasm/controls.rs
Normal file
318
wai/wai-quantum/src/qasm/controls.rs
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
//! A gate under controls: as the standard gates it is written in, and as the
|
||||
//! one op it stays where a method can run it whole.
|
||||
//!
|
||||
//! The standard form writes a phase on `m` qubits as `2^m − 1` `p` and `2^m − 2`
|
||||
//! `cx`, so its length doubles with each control ([`phase_n`]). A method that
|
||||
//! needs standard gates takes it (a device's compile, the tableau, a tensor
|
||||
//! network, the noisy methods, the exports); the state vectors do not. Reading
|
||||
//! a program with [`super::read_whole`], a gate whose standard form would double
|
||||
//! stays one op: its kind names the one-qubit gate, its `qubits` hold the
|
||||
//! controls and then the target ([`super::Op::is_whole`]). Everything else is
|
||||
//! written as [`super::read`] writes it, gate for gate.
|
||||
//!
|
||||
//! One set of functions writes both forms, through a [`Sink`], so the reader's
|
||||
//! standard form and [`super::Program::lowered`] of a whole program are one
|
||||
//! lowering.
|
||||
|
||||
use super::{Kind, NativeBase};
|
||||
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, PI};
|
||||
|
||||
/// Where a controlled gate's native form stands, for the bit-exact circuit.
|
||||
pub(crate) struct Nat {
|
||||
pub(crate) base: NativeBase,
|
||||
pub(crate) angle: f64,
|
||||
pub(crate) controls: Vec<u32>,
|
||||
pub(crate) target: u32,
|
||||
}
|
||||
|
||||
/// What a lowering writes its gates to.
|
||||
pub(crate) trait Sink {
|
||||
type Error;
|
||||
fn gate(&mut self, kind: Kind, params: Vec<f64>, qubits: Vec<u32>) -> Result<(), Self::Error>;
|
||||
/// Whether a gate whose standard form would double stays one op.
|
||||
fn whole(&self) -> bool;
|
||||
}
|
||||
|
||||
/// `x` on the last of `qs`, controlled by the others.
|
||||
pub(crate) fn x_n<S: Sink>(s: &mut S, qs: &[u32]) -> Result<(), S::Error> {
|
||||
let t = qs[qs.len() - 1];
|
||||
match qs.len() {
|
||||
1 => s.gate(Kind::X, vec![], qs.to_vec()),
|
||||
2 => s.gate(Kind::Cx, vec![], qs.to_vec()),
|
||||
3 => s.gate(Kind::Ccx, vec![], qs.to_vec()),
|
||||
_ if s.whole() => s.gate(Kind::X, vec![], qs.to_vec()),
|
||||
_ => {
|
||||
s.gate(Kind::H, vec![], vec![t])?;
|
||||
phase_n(s, PI, qs)?;
|
||||
s.gate(Kind::H, vec![], vec![t])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The phase e^{iθ} on the state in which all of `qs` are 1, and on no
|
||||
/// other: `p`, `cp`, `cz` or `ccz` where one is that gate, and otherwise
|
||||
/// from x₁⋯x_m = 2^{1−m} Σ_S (−1)^{|S|+1} ⊕_{i∈S} x_i over the nonempty
|
||||
/// subsets S of the m qubits. Each subset's parity is gathered on its
|
||||
/// highest qubit by `cx` in Gray-code order and turned by `p(±θ/2^{m−1})`:
|
||||
/// 2^m − 1 of each, exact, with no global phase, and dyadic when θ is.
|
||||
pub(crate) fn phase_n<S: Sink>(s: &mut S, theta: f64, qs: &[u32]) -> Result<(), S::Error> {
|
||||
let z = theta == PI;
|
||||
match qs.len() {
|
||||
0 => return Ok(()),
|
||||
1 if z => return s.gate(Kind::Z, vec![], qs.to_vec()),
|
||||
1 => return s.gate(Kind::P, vec![theta], qs.to_vec()),
|
||||
2 if z => return s.gate(Kind::Cz, vec![], qs.to_vec()),
|
||||
2 => return s.gate(Kind::Cp, vec![theta], qs.to_vec()),
|
||||
3 if z => return s.gate(Kind::Ccz, vec![], qs.to_vec()),
|
||||
_ => {}
|
||||
}
|
||||
if s.whole() {
|
||||
return if z { s.gate(Kind::Z, vec![], qs.to_vec()) } else { s.gate(Kind::P, vec![theta], qs.to_vec()) };
|
||||
}
|
||||
let a = theta / (1u64 << (qs.len() - 1)) as f64;
|
||||
for j in 0..qs.len() {
|
||||
let lead = qs[j];
|
||||
s.gate(Kind::P, vec![a], vec![lead])?;
|
||||
for i in 1u64..1 << j {
|
||||
// The Gray code's step from i − 1 to i flips this bit.
|
||||
let bit = i.trailing_zeros() as usize;
|
||||
s.gate(Kind::Cx, vec![], vec![qs[bit], lead])?;
|
||||
let size = (i ^ (i >> 1)).count_ones() + 1;
|
||||
s.gate(Kind::P, vec![if size % 2 == 1 { a } else { -a }], vec![lead])?;
|
||||
}
|
||||
// The code ends on the single bit j − 1: release it.
|
||||
if j > 0 {
|
||||
s.gate(Kind::Cx, vec![], vec![qs[j - 1], lead])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A one-qubit standard gate on `t` under the controls `cs` (at least
|
||||
/// one), exactly: the gate the standard names where there is one, and
|
||||
/// otherwise the target turned into a phase (`x` = `h·z·h`, `y` =
|
||||
/// `s·x·sdg`, `h` = `ry(π/4)·z·ry(−π/4)`, `rz(θ)` = `e^{−iθ/2}·p(θ)`, `u` its
|
||||
/// Z-Y-Z angles and their phase). Whole, a gate whose form here would go
|
||||
/// through [`phase_n`]'s doubling is one op instead.
|
||||
pub(crate) fn ctrl_one<S: Sink>(s: &mut S, base: Kind, p: &[f64], cs: &[u32], t: u32) -> Result<Option<Nat>, S::Error> {
|
||||
use Kind::*;
|
||||
let all: Vec<u32> = cs.iter().copied().chain([t]).collect();
|
||||
let one = cs.len() == 1;
|
||||
let nat = |base: NativeBase, angle: f64| Some(Nat { base, angle, controls: cs.to_vec(), target: t });
|
||||
Ok(match base {
|
||||
Id => None,
|
||||
X => {
|
||||
x_n(s, &all)?;
|
||||
nat(NativeBase::X, 0.0)
|
||||
}
|
||||
Y => {
|
||||
if one {
|
||||
s.gate(Cy, vec![], all)?;
|
||||
} else if s.whole() && cs.len() >= 3 {
|
||||
s.gate(Y, vec![], all)?;
|
||||
} else {
|
||||
s.gate(Sdg, vec![], vec![t])?;
|
||||
x_n(s, &all)?;
|
||||
s.gate(S, vec![], vec![t])?;
|
||||
}
|
||||
nat(NativeBase::Y, 0.0)
|
||||
}
|
||||
Z => {
|
||||
phase_n(s, PI, &all)?;
|
||||
nat(NativeBase::Z, 0.0)
|
||||
}
|
||||
H => {
|
||||
if one {
|
||||
s.gate(Ch, vec![], all)?;
|
||||
} else if s.whole() && cs.len() >= 3 {
|
||||
s.gate(H, vec![], all)?;
|
||||
} else {
|
||||
s.gate(Ry, vec![-FRAC_PI_4], vec![t])?;
|
||||
phase_n(s, PI, &all)?;
|
||||
s.gate(Ry, vec![FRAC_PI_4], vec![t])?;
|
||||
}
|
||||
nat(NativeBase::H, 0.0)
|
||||
}
|
||||
S | Sdg | T | Tdg | P => {
|
||||
let (b, theta) = match base {
|
||||
S => (NativeBase::S, FRAC_PI_2),
|
||||
Sdg => (NativeBase::Sdg, -FRAC_PI_2),
|
||||
T => (NativeBase::T, FRAC_PI_4),
|
||||
Tdg => (NativeBase::Tdg, -FRAC_PI_4),
|
||||
_ => (NativeBase::P, p[0]),
|
||||
};
|
||||
// A phase of π is `z`, which `phase_n` names.
|
||||
if s.whole() && !one && theta != PI {
|
||||
s.gate(base, p.to_vec(), all)?;
|
||||
} else {
|
||||
phase_n(s, theta, &all)?;
|
||||
}
|
||||
nat(b, theta)
|
||||
}
|
||||
Rz => {
|
||||
if one {
|
||||
s.gate(Crz, vec![p[0]], all)?;
|
||||
} else if s.whole() {
|
||||
s.gate(Rz, vec![p[0]], all)?;
|
||||
} else {
|
||||
phase_n(s, p[0], &all)?;
|
||||
phase_n(s, -p[0] / 2.0, cs)?;
|
||||
}
|
||||
None
|
||||
}
|
||||
Rx => {
|
||||
if one {
|
||||
s.gate(Crx, vec![p[0]], all)?;
|
||||
} else if s.whole() {
|
||||
s.gate(Rx, vec![p[0]], all)?;
|
||||
} else {
|
||||
s.gate(H, vec![], vec![t])?;
|
||||
ctrl_one(s, Rz, p, cs, t)?;
|
||||
s.gate(H, vec![], vec![t])?;
|
||||
}
|
||||
None
|
||||
}
|
||||
Ry => {
|
||||
if one {
|
||||
s.gate(Cry, vec![p[0]], all)?;
|
||||
} else if s.whole() {
|
||||
s.gate(Ry, vec![p[0]], all)?;
|
||||
} else {
|
||||
s.gate(Sdg, vec![], vec![t])?;
|
||||
ctrl_one(s, Rx, p, cs, t)?;
|
||||
s.gate(S, vec![], vec![t])?;
|
||||
}
|
||||
None
|
||||
}
|
||||
U => {
|
||||
if one {
|
||||
s.gate(Cu, vec![p[0], p[1], p[2], 0.0], all)?;
|
||||
} else if s.whole() {
|
||||
s.gate(U, p[..3].to_vec(), all)?;
|
||||
} else {
|
||||
// U(θ, φ, λ) = e^{i(φ+λ)/2} Rz(φ) Ry(θ) Rz(λ).
|
||||
ctrl_one(s, Rz, &[p[2]], cs, t)?;
|
||||
ctrl_one(s, Ry, &[p[0]], cs, t)?;
|
||||
ctrl_one(s, Rz, &[p[1]], cs, t)?;
|
||||
phase_n(s, (p[1] + p[2]) / 2.0, cs)?;
|
||||
}
|
||||
None
|
||||
}
|
||||
// sx = e^{iπ/4} rx(π/2).
|
||||
Sx | Sxdg => {
|
||||
if s.whole() && !one {
|
||||
s.gate(base, vec![], all)?;
|
||||
} else {
|
||||
let sign = if base == Sx { 1.0 } else { -1.0 };
|
||||
ctrl_one(s, Rx, &[sign * FRAC_PI_2], cs, t)?;
|
||||
phase_n(s, sign * FRAC_PI_4, cs)?;
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => unreachable!("not a one-qubit gate"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Any standard gate under the controls `cs`.
|
||||
pub(crate) fn ctrl_gate<S: Sink>(s: &mut S, kind: Kind, p: &[f64], q: &[u32], cs: &[u32]) -> Result<Option<Nat>, S::Error> {
|
||||
use Kind::*;
|
||||
let with = |extra: &[u32]| -> Vec<u32> { cs.iter().chain(extra).copied().collect() };
|
||||
Ok(match kind {
|
||||
Id | X | Y | Z | H | S | Sdg | T | Tdg | P | Rx | Ry | Rz | U | Sx | Sxdg => ctrl_one(s, kind, p, cs, q[0])?,
|
||||
Cx => ctrl_one(s, X, &[], &with(&q[..1]), q[1])?,
|
||||
Cy => ctrl_one(s, Y, &[], &with(&q[..1]), q[1])?,
|
||||
Cz => ctrl_one(s, Z, &[], &with(&q[..1]), q[1])?,
|
||||
Ch => ctrl_one(s, H, &[], &with(&q[..1]), q[1])?,
|
||||
Cp => ctrl_one(s, P, p, &with(&q[..1]), q[1])?,
|
||||
Crx => ctrl_one(s, Rx, p, &with(&q[..1]), q[1])?,
|
||||
Cry => ctrl_one(s, Ry, p, &with(&q[..1]), q[1])?,
|
||||
Crz => ctrl_one(s, Rz, p, &with(&q[..1]), q[1])?,
|
||||
Cu => {
|
||||
// Controlled e^{iγ} U(θ, φ, λ).
|
||||
let all = with(&q[..1]);
|
||||
ctrl_one(s, U, &p[..3], &all, q[1])?;
|
||||
phase_n(s, p[3], &all)?;
|
||||
None
|
||||
}
|
||||
Swap => {
|
||||
let (a, b) = (q[0], q[1]);
|
||||
if cs.len() == 1 {
|
||||
s.gate(Cswap, vec![], vec![cs[0], a, b])?;
|
||||
} else {
|
||||
s.gate(Cx, vec![], vec![b, a])?;
|
||||
ctrl_one(s, X, &[], &with(&[a]), b)?;
|
||||
s.gate(Cx, vec![], vec![b, a])?;
|
||||
}
|
||||
None
|
||||
}
|
||||
Iswap => {
|
||||
ctrl_gate(s, Rxx, &[-FRAC_PI_2], q, cs)?;
|
||||
ctrl_gate(s, Ryy, &[-FRAC_PI_2], q, cs)?;
|
||||
None
|
||||
}
|
||||
Rxx | Ryy => {
|
||||
let (a, b) = (q[0], q[1]);
|
||||
let pre: &[Kind] = if kind == Rxx { &[H] } else { &[Sdg, H] };
|
||||
for &k in pre {
|
||||
s.gate(k, vec![], vec![a])?;
|
||||
s.gate(k, vec![], vec![b])?;
|
||||
}
|
||||
ctrl_gate(s, Rzz, p, q, cs)?;
|
||||
let post: &[Kind] = if kind == Rxx { &[H] } else { &[H, S] };
|
||||
for &k in post {
|
||||
s.gate(k, vec![], vec![a])?;
|
||||
s.gate(k, vec![], vec![b])?;
|
||||
}
|
||||
None
|
||||
}
|
||||
Rzz => {
|
||||
// exp(−iθZZ/2) = cx · (1 ⊗ rz(θ)) · cx.
|
||||
s.gate(Cx, vec![], vec![q[0], q[1]])?;
|
||||
ctrl_one(s, Rz, p, cs, q[1])?;
|
||||
s.gate(Cx, vec![], vec![q[0], q[1]])?;
|
||||
None
|
||||
}
|
||||
Ccx => ctrl_one(s, X, &[], &with(&q[..2]), q[2])?,
|
||||
Ccz => ctrl_one(s, Z, &[], &with(&q[..2]), q[2])?,
|
||||
Cswap => ctrl_gate(s, Swap, &[], &q[1..], &with(&q[..1]))?,
|
||||
})
|
||||
}
|
||||
|
||||
/// How many standard gates `phase_n` writes for a phase on `m` qubits, `z`
|
||||
/// when the phase is π.
|
||||
fn phase_len(m: usize, z: bool) -> u128 {
|
||||
match m {
|
||||
0 => 0,
|
||||
1 | 2 => 1,
|
||||
3 if z => 1,
|
||||
// `2^m − 1` `p` and `2^m − 2` `cx`; past the width a count means
|
||||
// anything, it is simply more than any limit.
|
||||
m if m >= 120 => u128::MAX >> 8,
|
||||
m => (1u128 << (m + 1)) - 3,
|
||||
}
|
||||
}
|
||||
|
||||
/// How many standard gates the whole op of `kind` and `params` on `cs` and
|
||||
/// one target stands for, without writing them.
|
||||
pub(crate) fn whole_len(kind: Kind, params: &[f64], c: usize) -> u128 {
|
||||
use Kind::*;
|
||||
let m = c + 1;
|
||||
let rz = |c: usize, theta: f64| phase_len(c + 1, theta == PI).saturating_add(phase_len(c, -theta / 2.0 == PI));
|
||||
match kind {
|
||||
X => phase_len(m, true).saturating_add(2),
|
||||
Y => phase_len(m, true).saturating_add(4),
|
||||
Z => phase_len(m, true),
|
||||
H => phase_len(m, true).saturating_add(2),
|
||||
S | Sdg | T | Tdg => phase_len(m, false),
|
||||
P => phase_len(m, params[0] == PI),
|
||||
Rz => rz(c, params[0]),
|
||||
Rx => rz(c, params[0]).saturating_add(2),
|
||||
Ry => rz(c, params[0]).saturating_add(4),
|
||||
U => rz(c, params[2]).saturating_add(rz(c, params[0]).saturating_add(4)).saturating_add(rz(c, params[1])).saturating_add(phase_len(c, (params[1] + params[2]) / 2.0 == PI)),
|
||||
Sx | Sxdg => {
|
||||
let (r, g) = if kind == Sx { (FRAC_PI_2, FRAC_PI_4) } else { (-FRAC_PI_2, -FRAC_PI_4) };
|
||||
rz(c, r).saturating_add(2).saturating_add(phase_len(c, g == PI))
|
||||
}
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
|
@ -211,9 +211,9 @@ table! {
|
|||
NO_ROOT = "Q205", Refused, "`pow(r) @` with a fractional `r` where no exact gate exists here: a user gate, `u`, `cu`, or a rotation by 2π or more.";
|
||||
GLOBAL_PHASE = "Q206", Lowered, "`gphase(θ)`: a global phase, which no measurement sees, dropped; under controls it is a phase on the controls.";
|
||||
NO_CONTROLS = "Q207", Refused, "A control modifier that adds no control: `ctrl(0) @`.";
|
||||
TOO_MANY_CONTROLS = "Q208", Refused, "Modifiers that add more controls than the reader lowers (16).";
|
||||
TOO_MANY_CONTROLS = "Q208", Refused, "Modifiers that add more controls than the reader lowers (16 written in standard gates, 255 on a gate a method holds whole).";
|
||||
TOO_DEEP = "Q209", Refused, "Gate definitions or subroutine calls nested deeper than 32.";
|
||||
TOO_MANY_GATES = "Q210", Refused, "A program that expands to more gates than the reader holds (2 000 000).";
|
||||
TOO_MANY_GATES = "Q210", Refused, "A program that expands to more gates than the reader holds (2 000 000), or that holds gates whole on more qubits than that in all.";
|
||||
MODIFIER_PLACE = "Q211", Refused, "A modifier on something that is not a gate.";
|
||||
GATE_BODY = "Q212", Refused, "A statement a gate's body may not hold: a gate holds gate calls, `gphase`, `barrier`, loops and conditions known on reading.";
|
||||
|
||||
|
|
|
|||
|
|
@ -20,15 +20,21 @@ use super::diag::{self, Diagnostic};
|
|||
use super::lex::{Tok, Token, Verb};
|
||||
use super::parse::Version;
|
||||
use super::value::{self, Angle, Dur, Ty, Val};
|
||||
use super::controls::{self, Sink};
|
||||
use super::{
|
||||
Cond, Extra, Inputs, Instr, Kind, MAX_OPS, MAX_QUBITS, Native, NativeBase, Op, Output, OutputValue, Program, Timing, TimingKind, builtin, normalise,
|
||||
};
|
||||
|
||||
/// How deep gate definitions and subroutine calls may nest.
|
||||
pub(crate) const MAX_DEPTH: usize = 32;
|
||||
/// The most controls modifiers may add: a phase on m qubits is written as
|
||||
/// 2^m − 1 `cx` and as many `p`.
|
||||
/// The most controls modifiers may add where a gate is written in standard
|
||||
/// gates: a phase on m qubits is written as 2^m − 1 `p` and 2^m − 2 `cx`.
|
||||
pub(crate) const MAX_CONTROLS: u32 = 16;
|
||||
/// The most controls modifiers may add where a gate stays one op
|
||||
/// ([`super::read_whole`]): as many as the bit-exact circuit's op-log counts.
|
||||
pub(crate) const MAX_WHOLE_CONTROLS: u32 = 255;
|
||||
/// What a refusal past the standard form tells a job it can do.
|
||||
pub(crate) const HELD_WHOLE: &str = "a job on the state vector holds a gate under as many as 255 as one, and on the bit-exact method with \"options\": {\"controls\": \"whole\"}";
|
||||
/// The most statements and loop iterations a program may run on reading.
|
||||
pub(crate) const MAX_STEPS: u64 = 20_000_000;
|
||||
/// The most times one `while` may run on reading.
|
||||
|
|
@ -195,14 +201,6 @@ struct Frame {
|
|||
envs: Vec<Vec<Scope>>,
|
||||
}
|
||||
|
||||
/// Where a controlled gate's native form stands, for the bit-exact circuit.
|
||||
struct Nat {
|
||||
base: NativeBase,
|
||||
angle: f64,
|
||||
controls: Vec<u32>,
|
||||
target: u32,
|
||||
}
|
||||
|
||||
pub(crate) struct Elab<'a> {
|
||||
src: &'a str,
|
||||
version: Version,
|
||||
|
|
@ -245,6 +243,10 @@ pub(crate) struct Elab<'a> {
|
|||
written: BTreeSet<u32>,
|
||||
/// When a gradient is asked: how each gate depends on the parameters.
|
||||
tan: Option<Tan>,
|
||||
/// Whether a gate whose standard form would double stays one op.
|
||||
whole: bool,
|
||||
/// The qubits of the gates held whole so far.
|
||||
held: usize,
|
||||
}
|
||||
|
||||
/// The program `stmts` (read from `src`, its tokens `toks`) run into the
|
||||
|
|
@ -253,6 +255,12 @@ pub(crate) fn elaborate(src: &str, toks: &[Token], stmts: &[Stmt], version: Vers
|
|||
elaborate_with(src, toks, stmts, version, inputs, None).map(|(p, l, d, _)| (p, l, d))
|
||||
}
|
||||
|
||||
/// [`elaborate`], a gate whose standard form would double staying one op
|
||||
/// ([`super::controls`]).
|
||||
pub(crate) fn elaborate_whole(src: &str, toks: &[Token], stmts: &[Stmt], version: Version, inputs: &Inputs) -> R<(Program, Vec<usize>, Vec<Diagnostic>)> {
|
||||
elaborate_form(src, toks, stmts, version, inputs, None, true).map(|(p, l, d, _)| (p, l, d))
|
||||
}
|
||||
|
||||
/// One op's parameters' derivatives by each of the program's parameters.
|
||||
pub(crate) type OpTangents = Vec<Vec<f64>>;
|
||||
|
||||
|
|
@ -260,6 +268,11 @@ pub(crate) type OpTangents = Vec<Vec<f64>>;
|
|||
/// `tan` names ([`Tan`]): for each op, for each of its parameters, its
|
||||
/// derivative by each name.
|
||||
pub(crate) fn elaborate_with(src: &str, toks: &[Token], stmts: &[Stmt], version: Version, inputs: &Inputs, tan: Option<Tan>) -> R<(Program, Vec<usize>, Vec<Diagnostic>, Vec<OpTangents>)> {
|
||||
elaborate_form(src, toks, stmts, version, inputs, tan, false)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||
fn elaborate_form(src: &str, toks: &[Token], stmts: &[Stmt], version: Version, inputs: &Inputs, tan: Option<Tan>, whole: bool) -> R<(Program, Vec<usize>, Vec<Diagnostic>, Vec<OpTangents>)> {
|
||||
let mut e = Elab {
|
||||
src,
|
||||
version,
|
||||
|
|
@ -298,6 +311,8 @@ pub(crate) fn elaborate_with(src: &str, toks: &[Token], stmts: &[Stmt], version:
|
|||
ones: BTreeSet::new(),
|
||||
written: BTreeSet::new(),
|
||||
tan,
|
||||
whole,
|
||||
held: 0,
|
||||
};
|
||||
e.prescan(toks, stmts)?;
|
||||
e.block(stmts)?;
|
||||
|
|
@ -307,6 +322,23 @@ pub(crate) fn elaborate_with(src: &str, toks: &[Token], stmts: &[Stmt], version:
|
|||
Ok((p, l, d, tangents))
|
||||
}
|
||||
|
||||
/// The reader as what a controlled gate's lowering writes to: each gate an op
|
||||
/// and a step, at the statement that made it.
|
||||
struct Emit<'x, 'a> {
|
||||
e: &'x mut Elab<'a>,
|
||||
at: Loc,
|
||||
}
|
||||
|
||||
impl Sink for Emit<'_, '_> {
|
||||
type Error = Diagnostic;
|
||||
fn gate(&mut self, kind: Kind, params: Vec<f64>, qubits: Vec<u32>) -> R<()> {
|
||||
self.e.push(kind, params, qubits, self.at)
|
||||
}
|
||||
fn whole(&self) -> bool {
|
||||
self.e.whole
|
||||
}
|
||||
}
|
||||
|
||||
fn is_physical(name: &str) -> Option<u32> {
|
||||
name.strip_prefix('$').filter(|d| !d.is_empty() && d.bytes().all(|b| b.is_ascii_digit())).and_then(|d| d.parse().ok())
|
||||
}
|
||||
|
|
@ -452,14 +484,23 @@ impl<'a> Elab<'a> {
|
|||
if self.ops.len() >= MAX_OPS {
|
||||
return self.fail(diag::TOO_MANY_GATES, at, format!("more than {MAX_OPS} gates"));
|
||||
}
|
||||
for &q in &qubits {
|
||||
let op = Op { kind, qubits, params };
|
||||
// A gate held whole carries its qubits. Its standard gates are at least
|
||||
// as many, so whatever the standard form holds is within this.
|
||||
if op.is_whole() {
|
||||
self.held += op.qubits.len();
|
||||
if self.held > MAX_OPS {
|
||||
return self.fail(diag::TOO_MANY_GATES, at, format!("gates held whole on more than {MAX_OPS} qubits in all"));
|
||||
}
|
||||
}
|
||||
for &q in &op.qubits {
|
||||
self.touched[q as usize] = true;
|
||||
}
|
||||
if let Some(t) = self.tan.as_mut() {
|
||||
let next = t.next.take().unwrap_or_else(|| vec![t.zero(); params.len()]);
|
||||
let next = t.next.take().unwrap_or_else(|| vec![t.zero(); op.params.len()]);
|
||||
t.ops.push(next);
|
||||
}
|
||||
self.ops.push(Op { kind, qubits, params });
|
||||
self.ops.push(op);
|
||||
self.lines.push(self.top);
|
||||
self.steps.push(Instr::Gate(self.ops.len() - 1));
|
||||
Ok(())
|
||||
|
|
@ -488,10 +529,10 @@ impl<'a> Elab<'a> {
|
|||
let first = self.ops.len();
|
||||
let nat = match g {
|
||||
G::Phase(t) => {
|
||||
self.phase_n(*t, &cs, at)?;
|
||||
Some(Nat { base: NativeBase::P, angle: *t, controls: cs[..cs.len() - 1].to_vec(), target: cs[cs.len() - 1] })
|
||||
controls::phase_n(&mut Emit { e: self, at }, *t, &cs)?;
|
||||
Some(controls::Nat { base: NativeBase::P, angle: *t, controls: cs[..cs.len() - 1].to_vec(), target: cs[cs.len() - 1] })
|
||||
}
|
||||
G::Gate(k, ps, qs) => self.ctrl_gate(*k, ps, qs, &cs, at)?,
|
||||
G::Gate(k, ps, qs) => controls::ctrl_gate(&mut Emit { e: self, at }, *k, ps, qs, &cs)?,
|
||||
G::Barrier(_) => unreachable!(),
|
||||
};
|
||||
if let Some(n) = nat {
|
||||
|
|
@ -505,225 +546,6 @@ impl<'a> Elab<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// `x` on the last of `qs`, controlled by the others.
|
||||
fn x_n(&mut self, qs: &[u32], at: Loc) -> R<()> {
|
||||
let t = qs[qs.len() - 1];
|
||||
match qs.len() {
|
||||
1 => self.push(Kind::X, vec![], qs.to_vec(), at),
|
||||
2 => self.push(Kind::Cx, vec![], qs.to_vec(), at),
|
||||
3 => self.push(Kind::Ccx, vec![], qs.to_vec(), at),
|
||||
_ => {
|
||||
self.push(Kind::H, vec![], vec![t], at)?;
|
||||
self.phase_n(PI, qs, at)?;
|
||||
self.push(Kind::H, vec![], vec![t], at)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The phase e^{iθ} on the state in which all of `qs` are 1, and on no
|
||||
/// other: `p`, `cp`, `cz` or `ccz` where one is that gate, and otherwise
|
||||
/// from x₁⋯x_m = 2^{1−m} Σ_S (−1)^{|S|+1} ⊕_{i∈S} x_i over the nonempty
|
||||
/// subsets S of the m qubits. Each subset's parity is gathered on its
|
||||
/// highest qubit by `cx` in Gray-code order and turned by `p(±θ/2^{m−1})`:
|
||||
/// 2^m − 1 of each, exact, with no global phase, and dyadic when θ is.
|
||||
fn phase_n(&mut self, theta: f64, qs: &[u32], at: Loc) -> R<()> {
|
||||
let z = theta == PI;
|
||||
match qs.len() {
|
||||
0 => return Ok(()),
|
||||
1 if z => return self.push(Kind::Z, vec![], qs.to_vec(), at),
|
||||
1 => return self.push(Kind::P, vec![theta], qs.to_vec(), at),
|
||||
2 if z => return self.push(Kind::Cz, vec![], qs.to_vec(), at),
|
||||
2 => return self.push(Kind::Cp, vec![theta], qs.to_vec(), at),
|
||||
3 if z => return self.push(Kind::Ccz, vec![], qs.to_vec(), at),
|
||||
_ => {}
|
||||
}
|
||||
let a = theta / (1u64 << (qs.len() - 1)) as f64;
|
||||
for j in 0..qs.len() {
|
||||
let lead = qs[j];
|
||||
self.push(Kind::P, vec![a], vec![lead], at)?;
|
||||
for i in 1u64..1 << j {
|
||||
// The Gray code's step from i − 1 to i flips this bit.
|
||||
let bit = i.trailing_zeros() as usize;
|
||||
self.push(Kind::Cx, vec![], vec![qs[bit], lead], at)?;
|
||||
let size = (i ^ (i >> 1)).count_ones() + 1;
|
||||
self.push(Kind::P, vec![if size % 2 == 1 { a } else { -a }], vec![lead], at)?;
|
||||
}
|
||||
// The code ends on the single bit j − 1: release it.
|
||||
if j > 0 {
|
||||
self.push(Kind::Cx, vec![], vec![qs[j - 1], lead], at)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A one-qubit standard gate on `t` under the controls `cs` (at least
|
||||
/// one), exactly: the gate the standard names where there is one, and
|
||||
/// otherwise the target turned into a phase (`x` = `h·z·h`, `y` =
|
||||
/// `s·x·sdg`, `h` = `ry(π/4)·z·ry(−π/4)`, `rz(θ)` = `e^{−iθ/2}·p(θ)`, `u` its
|
||||
/// Z-Y-Z angles and their phase).
|
||||
fn ctrl_one(&mut self, base: Kind, p: &[f64], cs: &[u32], t: u32, at: Loc) -> R<Option<Nat>> {
|
||||
use Kind::*;
|
||||
let all: Vec<u32> = cs.iter().copied().chain([t]).collect();
|
||||
let one = cs.len() == 1;
|
||||
let nat = |base: NativeBase, angle: f64| Some(Nat { base, angle, controls: cs.to_vec(), target: t });
|
||||
Ok(match base {
|
||||
Id => None,
|
||||
X => {
|
||||
self.x_n(&all, at)?;
|
||||
nat(NativeBase::X, 0.0)
|
||||
}
|
||||
Y => {
|
||||
if one {
|
||||
self.push(Cy, vec![], all, at)?;
|
||||
} else {
|
||||
self.push(Sdg, vec![], vec![t], at)?;
|
||||
self.x_n(&all, at)?;
|
||||
self.push(S, vec![], vec![t], at)?;
|
||||
}
|
||||
nat(NativeBase::Y, 0.0)
|
||||
}
|
||||
Z => {
|
||||
self.phase_n(PI, &all, at)?;
|
||||
nat(NativeBase::Z, 0.0)
|
||||
}
|
||||
H => {
|
||||
if one {
|
||||
self.push(Ch, vec![], all, at)?;
|
||||
} else {
|
||||
self.push(Ry, vec![-FRAC_PI_4], vec![t], at)?;
|
||||
self.phase_n(PI, &all, at)?;
|
||||
self.push(Ry, vec![FRAC_PI_4], vec![t], at)?;
|
||||
}
|
||||
nat(NativeBase::H, 0.0)
|
||||
}
|
||||
S | Sdg | T | Tdg | P => {
|
||||
let (b, theta) = match base {
|
||||
S => (NativeBase::S, FRAC_PI_2),
|
||||
Sdg => (NativeBase::Sdg, -FRAC_PI_2),
|
||||
T => (NativeBase::T, FRAC_PI_4),
|
||||
Tdg => (NativeBase::Tdg, -FRAC_PI_4),
|
||||
_ => (NativeBase::P, p[0]),
|
||||
};
|
||||
self.phase_n(theta, &all, at)?;
|
||||
nat(b, theta)
|
||||
}
|
||||
Rz => {
|
||||
if one {
|
||||
self.push(Crz, vec![p[0]], all, at)?;
|
||||
} else {
|
||||
self.phase_n(p[0], &all, at)?;
|
||||
self.phase_n(-p[0] / 2.0, cs, at)?;
|
||||
}
|
||||
None
|
||||
}
|
||||
Rx => {
|
||||
if one {
|
||||
self.push(Crx, vec![p[0]], all, at)?;
|
||||
} else {
|
||||
self.push(H, vec![], vec![t], at)?;
|
||||
self.ctrl_one(Rz, p, cs, t, at)?;
|
||||
self.push(H, vec![], vec![t], at)?;
|
||||
}
|
||||
None
|
||||
}
|
||||
Ry => {
|
||||
if one {
|
||||
self.push(Cry, vec![p[0]], all, at)?;
|
||||
} else {
|
||||
self.push(Sdg, vec![], vec![t], at)?;
|
||||
self.ctrl_one(Rx, p, cs, t, at)?;
|
||||
self.push(S, vec![], vec![t], at)?;
|
||||
}
|
||||
None
|
||||
}
|
||||
U => {
|
||||
if one {
|
||||
self.push(Cu, vec![p[0], p[1], p[2], 0.0], all, at)?;
|
||||
} else {
|
||||
// U(θ, φ, λ) = e^{i(φ+λ)/2} Rz(φ) Ry(θ) Rz(λ).
|
||||
self.ctrl_one(Rz, &[p[2]], cs, t, at)?;
|
||||
self.ctrl_one(Ry, &[p[0]], cs, t, at)?;
|
||||
self.ctrl_one(Rz, &[p[1]], cs, t, at)?;
|
||||
self.phase_n((p[1] + p[2]) / 2.0, cs, at)?;
|
||||
}
|
||||
None
|
||||
}
|
||||
// sx = e^{iπ/4} rx(π/2).
|
||||
Sx | Sxdg => {
|
||||
let s = if base == Sx { 1.0 } else { -1.0 };
|
||||
self.ctrl_one(Rx, &[s * FRAC_PI_2], cs, t, at)?;
|
||||
self.phase_n(s * FRAC_PI_4, cs, at)?;
|
||||
None
|
||||
}
|
||||
_ => unreachable!("not a one-qubit gate"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Any standard gate under the controls `cs`.
|
||||
fn ctrl_gate(&mut self, kind: Kind, p: &[f64], q: &[u32], cs: &[u32], at: Loc) -> R<Option<Nat>> {
|
||||
use Kind::*;
|
||||
let with = |extra: &[u32]| -> Vec<u32> { cs.iter().chain(extra).copied().collect() };
|
||||
Ok(match kind {
|
||||
Id | X | Y | Z | H | S | Sdg | T | Tdg | P | Rx | Ry | Rz | U | Sx | Sxdg => self.ctrl_one(kind, p, cs, q[0], at)?,
|
||||
Cx => self.ctrl_one(X, &[], &with(&q[..1]), q[1], at)?,
|
||||
Cy => self.ctrl_one(Y, &[], &with(&q[..1]), q[1], at)?,
|
||||
Cz => self.ctrl_one(Z, &[], &with(&q[..1]), q[1], at)?,
|
||||
Ch => self.ctrl_one(H, &[], &with(&q[..1]), q[1], at)?,
|
||||
Cp => self.ctrl_one(P, p, &with(&q[..1]), q[1], at)?,
|
||||
Crx => self.ctrl_one(Rx, p, &with(&q[..1]), q[1], at)?,
|
||||
Cry => self.ctrl_one(Ry, p, &with(&q[..1]), q[1], at)?,
|
||||
Crz => self.ctrl_one(Rz, p, &with(&q[..1]), q[1], at)?,
|
||||
Cu => {
|
||||
// Controlled e^{iγ} U(θ, φ, λ).
|
||||
let all = with(&q[..1]);
|
||||
self.ctrl_one(U, &p[..3], &all, q[1], at)?;
|
||||
self.phase_n(p[3], &all, at)?;
|
||||
None
|
||||
}
|
||||
Swap => {
|
||||
let (a, b) = (q[0], q[1]);
|
||||
if cs.len() == 1 {
|
||||
self.push(Cswap, vec![], vec![cs[0], a, b], at)?;
|
||||
} else {
|
||||
self.push(Cx, vec![], vec![b, a], at)?;
|
||||
self.ctrl_one(X, &[], &with(&[a]), b, at)?;
|
||||
self.push(Cx, vec![], vec![b, a], at)?;
|
||||
}
|
||||
None
|
||||
}
|
||||
Iswap => {
|
||||
self.ctrl_gate(Rxx, &[-FRAC_PI_2], q, cs, at)?;
|
||||
self.ctrl_gate(Ryy, &[-FRAC_PI_2], q, cs, at)?;
|
||||
None
|
||||
}
|
||||
Rxx | Ryy => {
|
||||
let (a, b) = (q[0], q[1]);
|
||||
let pre: &[Kind] = if kind == Rxx { &[H] } else { &[Sdg, H] };
|
||||
for &k in pre {
|
||||
self.push(k, vec![], vec![a], at)?;
|
||||
self.push(k, vec![], vec![b], at)?;
|
||||
}
|
||||
self.ctrl_gate(Rzz, p, q, cs, at)?;
|
||||
let post: &[Kind] = if kind == Rxx { &[H] } else { &[H, S] };
|
||||
for &k in post {
|
||||
self.push(k, vec![], vec![a], at)?;
|
||||
self.push(k, vec![], vec![b], at)?;
|
||||
}
|
||||
None
|
||||
}
|
||||
Rzz => {
|
||||
// exp(−iθZZ/2) = cx · (1 ⊗ rz(θ)) · cx.
|
||||
self.push(Cx, vec![], vec![q[0], q[1]], at)?;
|
||||
self.ctrl_one(Rz, p, cs, q[1], at)?;
|
||||
self.push(Cx, vec![], vec![q[0], q[1]], at)?;
|
||||
None
|
||||
}
|
||||
Ccx => self.ctrl_one(X, &[], &with(&q[..2]), q[2], at)?,
|
||||
Ccz => self.ctrl_one(Z, &[], &with(&q[..2]), q[2], at)?,
|
||||
Cswap => self.ctrl_gate(Swap, &[], &q[1..], &with(&q[..1]), at)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn measure(&mut self, qs: &[u32], bs: &[u32], at: Loc) -> R<()> {
|
||||
if qs.len() != bs.len() {
|
||||
return self.fail(diag::MEASURE_LENGTHS, at, format!("{} qubits measured into {} bits", qs.len(), bs.len()));
|
||||
|
|
@ -1842,8 +1664,9 @@ impl<'a> Elab<'a> {
|
|||
}
|
||||
None => 1,
|
||||
};
|
||||
if polarity.len() as i128 + n > MAX_CONTROLS as i128 {
|
||||
return self.fail(diag::TOO_MANY_CONTROLS, *mat, format!("modifiers add at most {MAX_CONTROLS} controls here"));
|
||||
let most = if self.whole { MAX_WHOLE_CONTROLS } else { MAX_CONTROLS };
|
||||
if polarity.len() as i128 + n > most as i128 {
|
||||
return self.fail(diag::TOO_MANY_CONTROLS, *mat, format!("modifiers add at most {most} controls here{}", if self.whole { String::new() } else { format!(", written in standard gates; {HELD_WHOLE}") }));
|
||||
}
|
||||
polarity.extend(std::iter::repeat_n(*on, n as usize));
|
||||
}
|
||||
|
|
|
|||
381
wai/wai-quantum/src/qasm/whole_tests.rs
Normal file
381
wai/wai-quantum/src/qasm/whole_tests.rs
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
//! A gate under many controls, whole and in standard gates: the one lowering
|
||||
//! both forms share, and what each path gives.
|
||||
|
||||
use super::*;
|
||||
|
||||
const C3Z: &str = "qubit[5] q;\nh q;\nctrl(3) @ z q[0], q[1], q[2], q[3];\n";
|
||||
|
||||
/// Programs that hold gates whole, of every form the modifiers write, with
|
||||
/// what a program carries beside its gates.
|
||||
const WHOLE: &[&str] = &[
|
||||
C3Z,
|
||||
"qubit[5] q;\nh q;\nctrl(3) @ x q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[5] q;\nh q;\nctrl(3) @ y q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[5] q;\nh q;\nctrl(3) @ h q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ s q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ sdg q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ t q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ tdg q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ p(0.3) q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ rz(0.3) q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ rz(-2 * pi) q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ rz(pi) q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ rx(0.3) q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ ry(0.3) q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ u(0.3, 0.4, 0.5) q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ sx q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ sxdg q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl @ cu(0.3, 0.4, 0.5, 0.6) q[0], q[1], q[2];\n",
|
||||
"qubit[5] q;\nh q;\nctrl(2) @ cu(0.3, 0.4, 0.5, pi) q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[5] q;\nh q;\nctrl(2) @ cu(0.3, 0.4, 0.5, 0.6) q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(3) @ gphase(0.3) q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ swap q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ iswap q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ rzz(0.3) q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ rxx(0.3) q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ ryy(0.3) q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[3] q;\nh q;\nctrl(2) @ gphase(0.3) q[0], q[1];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(3) @ gphase(pi) q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nnegctrl(2) @ ctrl @ s q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[4] q;\nh q;\ninv @ ctrl(3) @ t q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[4] q;\nh q;\npow(2) @ ctrl(2) @ t q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nctrl(2) @ ctrl @ x q[0], q[1], q[2], q[3];\n",
|
||||
"gate g a, b { ctrl @ s a, b; h b; }\nqubit[4] q;\nh q;\nctrl(2) @ g q[0], q[1], q[2], q[3];\n",
|
||||
"qubit[3] a;\nqubit[3] b;\nh a;\nctrl(2) @ x a[0], a[1], a[2];\nctrl(3) @ z a[0], a[1], a[2], b[0];\nctrl(2) @ p(0.2) a[0], a[1], b[1];\n",
|
||||
// A step between gates, a barrier, a delay and an output carried past them.
|
||||
"qubit[5] q;\nbit[1] c;\nh q;\nc[0] = measure q[0];\nctrl(2) @ p(pi / 4) q[1], q[2], q[3];\nif (c[0] == 1) { ctrl(3) @ x q[1], q[2], q[3], q[4]; }\nctrl(2) @ t q[1], q[2], q[3];\nreset q[0];\n",
|
||||
"qubit[4] q;\nbit[4] c;\nh q;\nbarrier q;\nctrl(3) @ z q[0], q[1], q[2], q[3];\nbarrier q[0], q[1];\ndelay[20ns] q[2];\nctrl(2) @ t q[0], q[1], q[2];\nx q[0];\nc = measure q;\n",
|
||||
"OPENQASM 3.1;\nqubit[4] q;\noutput bit[2] o;\nh q;\nbox[100ns] { ctrl(2) @ p(0.5) q[0], q[1], q[2]; }\no[0] = measure q[0];\no[1] = measure q[3];\n",
|
||||
"OPENQASM 3.1;\ninput uint n;\nqubit[n] q;\nfor uint i in [2:n - 2] { ctrl(2) @ p(0.1 * i) q[i - 2], q[i - 1], q[i]; ctrl(3) @ x q[i - 2], q[i - 1], q[i], q[i + 1]; }\n",
|
||||
];
|
||||
|
||||
fn read_at(src: &str) -> (Inputs, Result<Reading, Diagnostic>) {
|
||||
let ins: Inputs = if src.contains("input uint n") { [("n".to_string(), 6)].into_iter().collect() } else { Inputs::new() };
|
||||
let r = read_with(src, &ins);
|
||||
(ins, r)
|
||||
}
|
||||
|
||||
/// What the reader gives in standard gates is what a whole reading lowers to,
|
||||
/// everything it carries included; and the count says so without writing them.
|
||||
#[test]
|
||||
fn a_whole_program_lowers_to_what_the_reader_writes() {
|
||||
let mut held = 0;
|
||||
for src in WHOLE {
|
||||
let (ins, standard) = read_at(src);
|
||||
let standard = standard.unwrap_or_else(|d| panic!("{d}\n{src}"));
|
||||
let whole = read_whole_with(src, &ins).unwrap();
|
||||
held += usize::from(whole.program.has_whole());
|
||||
assert_eq!(whole.diagnostics, standard.diagnostics, "{src}");
|
||||
assert_eq!(whole.program.standard_gates() as usize, standard.program.ops.len(), "{src}");
|
||||
let (lowered, lines) = whole.program.lowered_lines(&whole.lines).unwrap();
|
||||
assert_eq!(lowered, standard.program, "{src}");
|
||||
assert_eq!(lines, standard.lines, "{src}");
|
||||
// Lowered twice is lowered once; a standard program needs no writing.
|
||||
assert_eq!(lowered.lowered().unwrap(), lowered);
|
||||
assert!(matches!(lowered.standard_form().unwrap(), std::borrow::Cow::Borrowed(_)));
|
||||
}
|
||||
assert!(held >= WHOLE.len() - 3, "only {held} of {} held a gate whole", WHOLE.len());
|
||||
}
|
||||
|
||||
/// The conformance corpus's programs and the course's reach every construct:
|
||||
/// each reads to the same program by either way. A program with nothing to
|
||||
/// hold whole reads the same in both.
|
||||
#[test]
|
||||
fn every_program_of_the_reader_s_corpus_reads_alike_both_ways() {
|
||||
let bundle: serde_json::Value = serde_json::from_str(include_str!("../../../quantum-conformance/qasm3/bundle.json")).unwrap();
|
||||
let mut read_ok = 0;
|
||||
for case in bundle["cases"].as_array().unwrap() {
|
||||
let Some(src) = case["source"].as_str().or(case["text"].as_str()).or(case["qasm"].as_str()) else { continue };
|
||||
let ins: Inputs = case["inputs"].as_object().map(|o| o.iter().filter_map(|(k, v)| v.as_i64().map(|x| (k.clone(), x))).collect()).unwrap_or_default();
|
||||
match (read_with(src, &ins), read_whole_with(src, &ins)) {
|
||||
(Ok(s), Ok(w)) => {
|
||||
assert_eq!(w.program.lowered().unwrap(), s.program, "{src}");
|
||||
read_ok += 1;
|
||||
}
|
||||
(Err(a), Err(b)) => assert_eq!(a, b, "{src}"),
|
||||
(a, b) => panic!("{src}\nstandard {:?}\nwhole {:?}", a.err(), b.err()),
|
||||
}
|
||||
}
|
||||
assert!(read_ok >= 20, "only {read_ok} programs read");
|
||||
}
|
||||
|
||||
/// Exactly the gates whose standard form doubles stand whole.
|
||||
#[test]
|
||||
fn only_what_would_double_stands_whole() {
|
||||
let form = |src: &str| -> Vec<(Kind, usize)> { read_whole(src).unwrap().program.ops.iter().map(|o| (o.kind, o.qubits.len())).collect() };
|
||||
let at = |g: &str, c: usize| {
|
||||
let qs: Vec<String> = (0..c + 1).map(|i| format!("q[{i}]")).collect();
|
||||
form(&format!("qubit[{}] q;\nctrl({c}) @ {g} {};\n", c + 1, qs.join(", ")))
|
||||
};
|
||||
use Kind::*;
|
||||
assert_eq!(at("x", 1), [(Cx, 2)]);
|
||||
assert_eq!(at("x", 2), [(Ccx, 3)]);
|
||||
assert_eq!(at("x", 3), [(X, 4)]);
|
||||
assert_eq!(at("z", 2), [(Ccz, 3)]);
|
||||
assert_eq!(at("z", 3), [(Z, 4)]);
|
||||
assert_eq!(at("y", 2), [(Sdg, 1), (Ccx, 3), (S, 1)]);
|
||||
assert_eq!(at("y", 3), [(Y, 4)]);
|
||||
assert_eq!(at("h", 2), [(Ry, 1), (Ccz, 3), (Ry, 1)]);
|
||||
assert_eq!(at("h", 3), [(H, 4)]);
|
||||
assert_eq!(at("p(pi)", 2), [(Ccz, 3)]);
|
||||
assert_eq!(at("p(pi)", 3), [(Z, 4)]);
|
||||
assert_eq!(at("p(0.5)", 1), [(Cp, 2)]);
|
||||
assert_eq!(at("p(0.5)", 2), [(P, 3)]);
|
||||
assert_eq!(at("t", 2), [(T, 3)]);
|
||||
assert_eq!(at("rz(0.5)", 1), [(Crz, 2)]);
|
||||
assert_eq!(at("rz(0.5)", 2), [(Rz, 3)]);
|
||||
assert_eq!(at("u(0.1, 0.2, 0.3)", 1), [(Cu, 2)]);
|
||||
assert_eq!(at("u(0.1, 0.2, 0.3)", 2), [(U, 3)]);
|
||||
assert_eq!(at("sx", 2), [(Sx, 3)]);
|
||||
// The reader itself never gives one.
|
||||
for src in WHOLE {
|
||||
assert!(!read_at(src).1.unwrap().program.has_whole(), "{src}");
|
||||
}
|
||||
let g = &read_whole("qubit[4] q;\nctrl(3) @ x q[3], q[1], q[0], q[2];").unwrap().program.ops[0];
|
||||
assert!(g.is_whole());
|
||||
assert_eq!((g.controls(), g.qubits[3]), (&[3, 1, 0][..], 2));
|
||||
}
|
||||
|
||||
/// As many controls as a register holds, whole; in standard gates they are
|
||||
/// refused, each way saying what it can do.
|
||||
#[test]
|
||||
fn past_what_standard_gates_hold_a_gate_still_reads_whole() {
|
||||
let qs: Vec<String> = (0..21).map(|i| format!("q[{i}]")).collect();
|
||||
let src = format!("qubit[21] q;\nctrl(20) @ z {};\n", qs.join(", "));
|
||||
let d = read(&src).unwrap_err();
|
||||
assert!(d.code == diag::TOO_MANY_CONTROLS && d.message.contains("at most 16 controls") && d.message.contains("as many as 255") && d.message.contains("\"controls\": \"whole\""), "{d}");
|
||||
let p = read_whole(&src).unwrap().program;
|
||||
assert_eq!((p.ops.len(), p.standard_gates()), (1, (1 << 22) - 3));
|
||||
let d = p.lowered().unwrap_err();
|
||||
assert!(d.code == diag::TOO_MANY_CONTROLS, "{d}");
|
||||
// 16 controls fit one standard form, and many of them pass its limit.
|
||||
let qs: Vec<String> = (0..17).map(|i| format!("q[{i}]")).collect();
|
||||
let many = format!("qubit[17] q;\nfor uint i in [0:15] {{ ctrl(16) @ z {}; }}\n", qs.join(", "));
|
||||
let p = read_whole(&many).unwrap().program;
|
||||
assert_eq!(p.ops.len(), 16);
|
||||
let d = p.lowered().unwrap_err();
|
||||
assert!(d.code == diag::TOO_MANY_GATES, "{d}");
|
||||
assert_eq!(read(&many).unwrap_err().code, diag::TOO_MANY_GATES);
|
||||
let d = read_whole("qubit[300] q;\nctrl(256) @ z q[0:256];\n").unwrap_err();
|
||||
assert_eq!(d.code, diag::TOO_MANY_CONTROLS);
|
||||
}
|
||||
|
||||
/// The bit-exact circuit holds a gate whole as one gate under all its
|
||||
/// controls, or for an angle its phases, and touches nothing the controls
|
||||
/// leave off.
|
||||
#[test]
|
||||
fn the_bit_exact_circuit_holds_a_whole_gate_under_its_controls() {
|
||||
use crate::quantum::{BaseGate, Gate};
|
||||
let c = read_whole(C3Z).unwrap().program.dyadic().unwrap();
|
||||
assert_eq!(c.ops.len(), 6);
|
||||
assert_eq!(c.ops[5], Gate { base: BaseGate::Z, controls: vec![0, 1, 2], target: 3, param: 0 });
|
||||
for (src, ops) in [
|
||||
("qubit[4] q;\nctrl(3) @ x q[0], q[1], q[2], q[3];\n", 1),
|
||||
("qubit[4] q;\nctrl(3) @ y q[0], q[1], q[2], q[3];\n", 1),
|
||||
("qubit[4] q;\nctrl(3) @ h q[0], q[1], q[2], q[3];\n", 1),
|
||||
("qubit[4] q;\nctrl(2) @ t q[0], q[1], q[2];\n", 1),
|
||||
// One phase gate per set bit of the angle's numerator.
|
||||
("qubit[4] q;\nctrl(2) @ p(3 * pi / 4) q[0], q[1], q[2];\n", 2),
|
||||
// p(π/2) under both, and −π/4, seven eighths of a turn, three bits.
|
||||
("qubit[4] q;\nctrl(2) @ rz(pi / 2) q[0], q[1], q[2];\n", 4),
|
||||
("qubit[4] q;\nctrl(2) @ rx(pi / 2) q[0], q[1], q[2];\n", 6),
|
||||
] {
|
||||
let c = read_whole(src).unwrap().program.dyadic().unwrap();
|
||||
assert_eq!(c.ops.len(), ops, "{src}");
|
||||
}
|
||||
// A phase finer than its own table once halved 2^m times stays in reach.
|
||||
let fine = "qubit[6] q;\nctrl(5) @ p(2 * pi / 2**32) q[0], q[1], q[2], q[3], q[4], q[5];\n";
|
||||
assert!(read(fine).unwrap().program.dyadic().is_err() && read_whole(fine).unwrap().program.dyadic().is_ok());
|
||||
// The control's phase stays right when the state has one: rz(2π) is −1.
|
||||
let sv = read_whole("qubit[3] q;\nx q[0];\nx q[1];\nctrl(2) @ rz(2 * pi) q[0], q[1], q[2];\n").unwrap().program.dyadic().unwrap().simulate().unwrap();
|
||||
assert_eq!(sv.amps[0b011].re, -crate::quantum::ONE);
|
||||
}
|
||||
|
||||
/// The ideal state, in double precision, with qubit 0 the lowest bit of the
|
||||
/// index as on the bit-exact method.
|
||||
fn ideal(p: &Program) -> Vec<(f64, f64)> {
|
||||
let mut sv = crate::quantum_sv::StateVector::zero(p.n_qubits).unwrap();
|
||||
sv.run_ops(&p.sv_ops(), 1).unwrap();
|
||||
let n = p.n_qubits;
|
||||
(0..1usize << n).map(|x| x.reverse_bits() >> (usize::BITS - n)).map(|r| (sv.re[r], sv.im[r])).collect()
|
||||
}
|
||||
|
||||
/// The Euclidean distance of a fixed-point state from an ideal one, the phase
|
||||
/// that aligns them taken out.
|
||||
fn distance(amps: &[crate::quantum::Amp], ideal: &[(f64, f64)]) -> f64 {
|
||||
let scale = crate::quantum::ONE as f64;
|
||||
let (mut re, mut im) = (0.0, 0.0);
|
||||
for (a, b) in amps.iter().zip(ideal) {
|
||||
let (x, y) = (a.re as f64 / scale, a.im as f64 / scale);
|
||||
re += x * b.0 + y * b.1;
|
||||
im += y * b.0 - x * b.1;
|
||||
}
|
||||
let m = re.hypot(im).max(f64::MIN_POSITIVE);
|
||||
let (cr, ci) = (re / m, -im / m);
|
||||
let mut d = 0.0;
|
||||
for (a, b) in amps.iter().zip(ideal) {
|
||||
let (x, y) = (a.re as f64 / scale, a.im as f64 / scale);
|
||||
let (u, v) = (x * cr - y * ci - b.0, x * ci + y * cr - b.1);
|
||||
d += u * u + v * v;
|
||||
}
|
||||
d.sqrt()
|
||||
}
|
||||
|
||||
/// What the old expansion and the whole gate give on the bit-exact method. They
|
||||
/// are not the same bits where the expansion goes through inexact phases: its
|
||||
/// `P(±θ/2^{m−1})` round at every amplitude, and the whole gate has no such
|
||||
/// steps, so its state is nearer the ideal one, and both within the published
|
||||
/// bound. Where the standard form writes no doubling they are the same op-log,
|
||||
/// and so the same bits. Pinned here, so that every host holds both.
|
||||
#[test]
|
||||
fn the_whole_gate_is_nearer_the_ideal_state_than_its_expansion() {
|
||||
let rows: &[(&str, &str, &str)] = &[
|
||||
// Program, the expansion's state hash (as the engine gave before gates
|
||||
// stood whole, on arm64 and x86_64), and the whole gate's.
|
||||
(C3Z, "181acaa4aad01230", "2402400329d5"),
|
||||
("qubit[5] q;\nh q;\nctrl(3) @ x q[0], q[1], q[2], q[3];\n", "8a504be7fc0299e8", "50b01fab0bfe"),
|
||||
("qubit[4] q;\nh q;\nctrl(2) @ p(pi / 8) q[0], q[1], q[2];\n", "13346662e1739d87", "d80c6943dc57"),
|
||||
];
|
||||
let hex = |b: &[u8]| b.iter().map(|x| format!("{x:02x}")).collect::<String>();
|
||||
for (src, old, new) in rows {
|
||||
let (standard, whole) = (read(src).unwrap().program.dyadic().unwrap(), read_whole(src).unwrap().program.dyadic().unwrap());
|
||||
assert!(whole.ops.len() < standard.ops.len());
|
||||
let (a, b) = (standard.simulate().unwrap(), whole.simulate().unwrap());
|
||||
assert_eq!(&hex(&a.statevector_hash())[..16], *old, "the standard form's bits moved: {src}");
|
||||
assert!(hex(&b.statevector_hash()).starts_with(new), "{src}: {}", hex(&b.statevector_hash()));
|
||||
assert_ne!(a.amps, b.amps, "{src}");
|
||||
let id = ideal(&read_whole(src).unwrap().program);
|
||||
let (da, db) = (distance(&a.amps, &id), distance(&b.amps, &id));
|
||||
// Rounding is bounded by quantum-sim.md §5: 1 + √2/2 units per component
|
||||
// per inexact gate; here far fewer gates than components.
|
||||
let unit = (crate::quantum::ONE as f64).recip();
|
||||
let bound = (1.0 + std::f64::consts::FRAC_1_SQRT_2) * unit * (2.0 * id.len() as f64).sqrt() * standard.ops.len() as f64;
|
||||
assert!(da <= bound && db <= bound, "{src}: {da} and {db} against {bound}");
|
||||
assert!(db < da, "{src}: the whole gate {db}, its expansion {da}");
|
||||
}
|
||||
// Where nothing doubles, the op-log is one and the bits are one.
|
||||
for src in [
|
||||
"qubit[4] q;\nh q;\nt q[0];\nctrl(2) @ x q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nt q[0];\nctrl(2) @ z q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nt q[0];\nctrl(2) @ y q[0], q[1], q[2];\n",
|
||||
"qubit[4] q;\nh q;\nt q[0];\nctrl(2) @ h q[0], q[1], q[2];\n",
|
||||
"qubit[3] q;\nh q;\nctrl @ p(pi / 8) q[0], q[1];\n",
|
||||
"qubit[3] q;\nh q;\nctrl @ swap q[0], q[1], q[2];\n",
|
||||
"qubit[3] q;\nh q;\ncx q[0], q[1];\nrz(pi / 4) q[2];\n",
|
||||
] {
|
||||
let (a, b) = (read(src).unwrap().program, read_whole(src).unwrap().program);
|
||||
assert_eq!(a, b, "{src}");
|
||||
assert_eq!(a.dyadic().unwrap().ops, b.dyadic().unwrap().ops, "{src}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The double-precision state vector of a whole gate against its expansion:
|
||||
/// within the double-precision bound of the expansion's gates, for every form.
|
||||
#[test]
|
||||
fn the_state_vector_of_a_whole_gate_is_its_expansion_s_within_rounding() {
|
||||
for src in WHOLE.iter().filter(|s| !s.contains("measure") && !s.contains("input")) {
|
||||
let whole = read_whole(src).unwrap().program;
|
||||
let standard = read(src).unwrap().program;
|
||||
for width in [1, 3] {
|
||||
let mut a = crate::quantum_sv::StateVector::zero(standard.n_qubits).unwrap();
|
||||
a.run(&standard.matrices(), width).unwrap();
|
||||
let mut b = crate::quantum_sv::StateVector::zero(whole.n_qubits).unwrap();
|
||||
b.run_ops(&whole.sv_ops(), width).unwrap();
|
||||
let d: f64 = a.re.iter().zip(&b.re).chain(a.im.iter().zip(&b.im)).map(|(x, y)| (x - y) * (x - y)).sum::<f64>().sqrt();
|
||||
// C(w) ≈ 64 units of 2^-53 per gate at this width, as in the corpus's bound.
|
||||
let bound = standard.ops.len() as f64 * 64.0 * (f64::EPSILON / 2.0);
|
||||
assert!(d <= bound, "{src}: {d} against {bound} at width {width}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A program with no gate held whole runs as `matrices` and `run` run it, to
|
||||
/// the bit, at every fusion width.
|
||||
#[test]
|
||||
fn a_run_without_a_whole_gate_is_the_run_it_was() {
|
||||
let src = "qubit[12] q;\nh q;\nfor uint i in [0:10] { cp(0.3 * i) q[i], q[i + 1]; rx(0.1 * i) q[i]; }\nccx q[0], q[3], q[7];\n";
|
||||
let p = read_whole(src).unwrap().program;
|
||||
assert!(!p.has_whole());
|
||||
for width in 1..=5 {
|
||||
let mut a = crate::quantum_sv::StateVector::zero(12).unwrap();
|
||||
a.run(&p.matrices(), width).unwrap();
|
||||
let mut b = crate::quantum_sv::StateVector::zero(12).unwrap();
|
||||
b.run_ops(&p.sv_ops(), width).unwrap();
|
||||
assert_eq!(a, b, "width {width}");
|
||||
}
|
||||
}
|
||||
|
||||
/// `matrices`, `rotations` and the tableau on a whole program write its
|
||||
/// standard gates, and a whole gate that is not Clifford says so at its first
|
||||
/// standard gate that is not.
|
||||
#[test]
|
||||
fn a_lowering_that_needs_standard_gates_writes_them() {
|
||||
for src in [C3Z, "qubit[4] q;\nh q;\nctrl(2) @ rz(0.3) q[0], q[1], q[2];\n"] {
|
||||
let (w, s) = (read_whole(src).unwrap().program, read(src).unwrap().program);
|
||||
let (a, b) = (w.matrices(), s.matrices());
|
||||
assert_eq!(a.len(), b.len());
|
||||
assert!(a.iter().zip(&b).all(|(x, y)| x.qubits == y.qubits && x.matrix == y.matrix));
|
||||
assert_eq!(w.rotations().ops.len(), s.rotations().ops.len());
|
||||
assert_eq!(w.local_matrices().len(), s.local_matrices().len());
|
||||
assert!(w.clifford().is_err() && s.clifford().is_err());
|
||||
}
|
||||
// rz(2π) under two controls is a controlled Z on them: Clifford either way.
|
||||
let src = "qubit[3] q;\nctrl(2) @ rz(2 * pi) q[0], q[1], q[2];\n";
|
||||
let (w, s) = (read_whole(src).unwrap().program, read(src).unwrap().program);
|
||||
assert!(w.has_whole());
|
||||
assert_eq!(format!("{:?}", w.clifford().unwrap()), format!("{:?}", s.clifford().unwrap()));
|
||||
assert!(w.clifford_t().is_ok() == s.clifford_t().is_ok());
|
||||
// Under 13 controls the refusal comes from one gate, not 32765.
|
||||
let qs: Vec<String> = (0..14).map(|i| format!("q[{i}]")).collect();
|
||||
let big = read_whole(&format!("qubit[14] q;\nctrl(13) @ z {};\n", qs.join(", "))).unwrap().program;
|
||||
assert!(big.clifford().unwrap_err().contains("is not a Clifford gate"));
|
||||
}
|
||||
|
||||
/// The bit-exact lowering's report: the phase its gates drop, exactly, whole
|
||||
/// turns counted, and the angles it read as another.
|
||||
#[test]
|
||||
fn the_bit_exact_report_states_what_the_lowering_did() {
|
||||
let report = |src: &str| read_whole(src).unwrap().program.dyadic_with_report().unwrap().1;
|
||||
// No phase: none dropped, none read.
|
||||
assert_eq!(report("qubit[2] q;\nh q[0];\ncx q[0], q[1];\np(pi / 4) q[0];\ncrz(pi / 2) q[0], q[1];\n"), DyadicReport::default());
|
||||
// rz(θ) = e^{−iθ/2} p(θ): −π/8 for θ = π/4, in 2^33rds of a turn.
|
||||
let r = report("qubit[1] q;\nrz(pi / 4) q[0];\n");
|
||||
assert_eq!(r.global_phase, (1 << 33) - (1 << 29));
|
||||
assert!((r.global_phase_radians() + std::f64::consts::FRAC_PI_8).abs() < 1e-15);
|
||||
// A whole turn is −1, a half-turn's worth of phase, and two are 1.
|
||||
assert_eq!(report("qubit[1] q;\nrz(2 * pi) q[0];\n").global_phase, 1 << 32);
|
||||
assert_eq!(report("qubit[1] q;\nrz(4 * pi) q[0];\n").global_phase, 0);
|
||||
for g in ["rx(pi / 2) q[0]", "ry(pi / 2) q[0]", "u(pi / 2, pi / 4, pi / 8) q[0]", "rzz(pi / 2) q[0], q[1]", "rxx(pi / 2) q[0], q[1]", "ryy(pi / 2) q[0], q[1]"] {
|
||||
assert_eq!(report(&format!("qubit[2] q;\n{g};\n")).global_phase, (1 << 33) - (1 << 30), "{g}");
|
||||
}
|
||||
// Under a control the phase is kept, whole or written out.
|
||||
assert_eq!(report("qubit[3] q;\nctrl(2) @ rz(pi / 2) q[0], q[1], q[2];\n").global_phase, 0);
|
||||
assert_eq!(report("qubit[2] q;\ncrz(pi / 2) q[0], q[1];\n").global_phase, 0);
|
||||
// Below every entry of the table: not refused, and not silent.
|
||||
let tiny = |k: u32| format!("qubit[1] q;\np(2 * pi / 2**{k}) q[0];\n");
|
||||
for k in [47, 48, 52, 60] {
|
||||
let r = report(&tiny(k));
|
||||
assert_eq!(r.below_table, 1, "k = {k}");
|
||||
assert!((r.largest_below_table / (std::f64::consts::TAU / 2f64.powi(k as i32)) - 1.0).abs() < 1e-3, "k = {k}: {}", r.largest_below_table);
|
||||
}
|
||||
assert_eq!(report(&tiny(32)).below_table, 0);
|
||||
assert!(read_whole(&tiny(40)).unwrap().program.dyadic().unwrap_err().ends_with("[Q500]"));
|
||||
// A double's rounding of an angle on the grid moves nothing; more does.
|
||||
assert_eq!(report("qubit[1] q;\np(pi / 7 * 7 / 4) q[0];\nrz(pi / 3 * 3) q[0];\n").moved, 0);
|
||||
let moved = report("qubit[1] q;\np(pi / 2 + 2 * pi / 2**48) q[0];\n");
|
||||
assert_eq!((moved.moved, moved.below_table), (1, 0));
|
||||
assert!(moved.largest_moved > 0.0);
|
||||
}
|
||||
|
||||
/// A gate held whole carries its qubits: what a program may hold counts them,
|
||||
/// so a loop of wide gates is refused where it would have taken gigabytes, and
|
||||
/// nothing the standard form could hold is.
|
||||
#[test]
|
||||
fn a_loop_of_wide_whole_gates_is_held_to_what_a_program_may_hold() {
|
||||
let qs: Vec<String> = (0..255).map(|i| format!("q[{i}]")).collect();
|
||||
let src = format!("qubit[255] q;\nfor int i in [1:100000] {{\n ctrl(254) @ p(0.1) {};\n}}\n", qs.join(", "));
|
||||
let d = read_whole(&src).err().unwrap();
|
||||
assert!(d.code == diag::TOO_MANY_GATES && d.line() == Some(3), "{d}");
|
||||
let src = format!("qubit[255] q;\nfor int i in [1:1000] {{\n ctrl(254) @ p(0.1) {};\n}}\n", qs.join(", "));
|
||||
assert_eq!(read_whole(&src).unwrap().program.ops.len(), 1000);
|
||||
}
|
||||
|
|
@ -722,6 +722,11 @@ impl Circuit {
|
|||
/// The shortcuts read only the matrix, so they hold for either gateset's table:
|
||||
/// circuit2's `P(k ≥ 18)` has `re == ONE` and `im ≠ 0`, which is the diagonal
|
||||
/// path, and only an exact `(−ONE, 0)` takes the negation.
|
||||
///
|
||||
/// Under two or more controls the scan gives way to a walk of only the indices
|
||||
/// the controls leave on ([`apply_under_controls`]): no pair depends on another,
|
||||
/// so the order of the visits changes no bit, and a gate under `c` controls
|
||||
/// costs `2^(n − c)`.
|
||||
fn apply(amps: &mut [Amp], n: u8, g: &Gate, gs: Gateset) {
|
||||
if matches!(g.base, BaseGate::I) {
|
||||
return;
|
||||
|
|
@ -730,6 +735,11 @@ fn apply(amps: &mut [Amp], n: u8, g: &Gate, gs: Gateset) {
|
|||
let tbit = 1usize << g.target;
|
||||
let ctrl_mask: usize = g.controls.iter().fold(0usize, |acc, &c| acc | (1usize << c));
|
||||
let dim = 1usize << n;
|
||||
// A repeated control (circuit/1 keeps them) counts once in the mask.
|
||||
if ctrl_mask.count_ones() >= 2 {
|
||||
apply_under_controls(amps, dim, tbit, ctrl_mask, m);
|
||||
return;
|
||||
}
|
||||
|
||||
let is_zero = |a: Amp| a.re == 0 && a.im == 0;
|
||||
let is_one = |a: Amp| a.re == ONE && a.im == 0;
|
||||
|
|
@ -814,6 +824,45 @@ fn apply(amps: &mut [Amp], n: u8, g: &Gate, gs: Gateset) {
|
|||
}
|
||||
}
|
||||
|
||||
/// [`apply`] under controls: the same expression for each pair as the scan's,
|
||||
/// its pairs found directly. The next free index comes from the last by
|
||||
/// carrying through the fixed bits.
|
||||
fn apply_under_controls(amps: &mut [Amp], dim: usize, tbit: usize, ctrl_mask: usize, m: [[Amp; 2]; 2]) {
|
||||
let is_zero = |a: Amp| a.re == 0 && a.im == 0;
|
||||
let is_one = |a: Amp| a.re == ONE && a.im == 0;
|
||||
let fixed = ctrl_mask | tbit;
|
||||
let groups = dim >> fixed.count_ones();
|
||||
let diagonal = is_one(m[0][0]) && is_zero(m[0][1]) && is_zero(m[1][0]);
|
||||
let negate = diagonal && m[1][1].re == -ONE && m[1][1].im == 0;
|
||||
let anti = is_zero(m[0][0]) && is_zero(m[1][1]);
|
||||
let swap = anti && is_one(m[0][1]) && is_one(m[1][0]);
|
||||
let real = m[0][0].im == 0 && m[0][1].im == 0 && m[1][0].im == 0 && m[1][1].im == 0;
|
||||
let mut x = 0usize;
|
||||
for _ in 0..groups {
|
||||
let (i, j) = (x | ctrl_mask, x | fixed);
|
||||
if negate {
|
||||
amps[j] = Amp { re: -amps[j].re, im: -amps[j].im };
|
||||
} else if diagonal {
|
||||
amps[j] = m[1][1].mul(amps[j]);
|
||||
} else if swap {
|
||||
amps.swap(i, j);
|
||||
} else if anti {
|
||||
let (a0, a1) = (amps[i], amps[j]);
|
||||
amps[i] = m[0][1].mul(a1);
|
||||
amps[j] = m[1][0].mul(a0);
|
||||
} else if real {
|
||||
let (a0, a1) = (amps[i], amps[j]);
|
||||
amps[i] = Amp { re: fxmul(m[0][0].re, a0.re) + fxmul(m[0][1].re, a1.re), im: fxmul(m[0][0].re, a0.im) + fxmul(m[0][1].re, a1.im) };
|
||||
amps[j] = Amp { re: fxmul(m[1][0].re, a0.re) + fxmul(m[1][1].re, a1.re), im: fxmul(m[1][0].re, a0.im) + fxmul(m[1][1].re, a1.im) };
|
||||
} else {
|
||||
let (a0, a1) = (amps[i], amps[j]);
|
||||
amps[i] = m[0][0].mul(a0).add(m[0][1].mul(a1));
|
||||
amps[j] = m[1][0].mul(a0).add(m[1][1].mul(a1));
|
||||
}
|
||||
x = (x | fixed).wrapping_add(1) & !fixed;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// State vector + conformance
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -1623,6 +1672,58 @@ pub fn accept_corpus() -> Vec<(&'static str, Vec<u8>, Circuit, Option<Measure>)>
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A gate under two or more controls walks only its pairs: the bits of the
|
||||
/// plain scan of every index, for each base gate, in each gateset, at any
|
||||
/// choice of controls, a repeated control of circuit/1 among them.
|
||||
#[test]
|
||||
fn the_walk_under_controls_gives_the_scan_s_bits() {
|
||||
fn scan(amps: &mut [Amp], g: &Gate, gs: Gateset) {
|
||||
if matches!(g.base, BaseGate::I) {
|
||||
return;
|
||||
}
|
||||
let m = g.base.matrix(g.param, gs);
|
||||
let tbit = 1usize << g.target;
|
||||
let mask = g.controls.iter().fold(0usize, |a, &c| a | 1usize << c);
|
||||
for i in 0..amps.len() {
|
||||
if i & tbit == 0 && i & mask == mask {
|
||||
let j = i | tbit;
|
||||
let (a0, a1) = (amps[i], amps[j]);
|
||||
amps[i] = m[0][0].mul(a0).add(m[0][1].mul(a1));
|
||||
amps[j] = m[1][0].mul(a0).add(m[1][1].mul(a1));
|
||||
}
|
||||
}
|
||||
}
|
||||
for gs in [Gateset::V1, Gateset::V2] {
|
||||
let mut prep = Circuit::with_gateset(9, gs);
|
||||
for q in 0..9 {
|
||||
prep.h(q);
|
||||
prep.t(q);
|
||||
prep.p(5 + u16::from(q), q);
|
||||
}
|
||||
for q in 0..8 {
|
||||
prep.cx(q, q + 1);
|
||||
}
|
||||
let start = prep.simulate().unwrap();
|
||||
for (controls, target) in [(vec![0u8, 1], 2u8), (vec![8, 3], 0), (vec![1, 5, 7], 8), (vec![0, 1, 2, 3, 4, 5, 6, 7], 8), (vec![8, 2, 4, 6], 3)] {
|
||||
let bases = [(BaseGate::I, 0), (BaseGate::X, 0), (BaseGate::Y, 0), (BaseGate::Z, 0), (BaseGate::H, 0), (BaseGate::S, 0), (BaseGate::Sdg, 0), (BaseGate::T, 0), (BaseGate::Tdg, 0), (BaseGate::P, 1), (BaseGate::P, 2), (BaseGate::P, 9), (BaseGate::P, 32)];
|
||||
for (base, param) in bases {
|
||||
let g = Gate { base, controls: controls.clone(), target, param };
|
||||
let (mut a, mut b) = (start.amps.clone(), start.amps.clone());
|
||||
apply(&mut a, 9, &g, gs);
|
||||
scan(&mut b, &g, gs);
|
||||
assert_eq!(a, b, "{g:?}");
|
||||
}
|
||||
}
|
||||
if gs == Gateset::V1 {
|
||||
let g = Gate { base: BaseGate::H, controls: vec![0, 0, 1], target: 2, param: 0 };
|
||||
let (mut a, mut b) = (start.amps.clone(), start.amps.clone());
|
||||
apply(&mut a, 9, &g, gs);
|
||||
scan(&mut b, &g, gs);
|
||||
assert_eq!(a, b, "a repeated control");
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The pre-optimisation `apply`, kept verbatim as an oracle. The fast paths
|
||||
/// are justified by an argument (`fxmul(ONE,x) == x`, `fxmul(0,x) == 0`), and
|
||||
/// an argument about fixed-point rounding is exactly the kind that is
|
||||
|
|
|
|||
|
|
@ -43,14 +43,28 @@
|
|||
//! instructions an error-correction experiment uses:
|
||||
//! - resets and measurements in Z and X, with measurement flip noise, and
|
||||
//! inverted measurement targets (`M !3`);
|
||||
//! - `H`, `S`, `√X`, Paulis, `CX`, `CZ`, `SWAP`;
|
||||
//! - X/Y/Z flips and one- and two-qubit depolarizing noise;
|
||||
//! - `H`, `S`, `√X`, Paulis, the axis-cycling `C_XYZ` and `C_ZYX`, `CX`, `CZ`,
|
||||
//! `SWAP`;
|
||||
//! - X/Y/Z flips, one- and two-qubit depolarizing noise, correlated errors
|
||||
//! (`E(p) X1 Y2 Z3`, also `CORRELATED_ERROR`) and one-qubit Pauli channels
|
||||
//! (`PAULI_CHANNEL_1(px, py, pz)`);
|
||||
//! - detectors with their coordinates (`SHIFT_COORDS` applied), observables
|
||||
//! on measurement records and on Pauli targets (`OBSERVABLE_INCLUDE(0) X3
|
||||
//! Z4`), and `REPEAT` blocks;
|
||||
//! - a tag on any instruction (`X_ERROR[leak](0.01) 2`): the tag of a noise
|
||||
//! instruction is the tag of every mechanism it makes.
|
||||
//!
|
||||
//! A correlated error is a flip on a qubit of its own, handed to its targets by
|
||||
//! `CX` and `CZ` and reset: a circuit with one holds one qubit more than its
|
||||
//! text names ([`Circuit::qubits`]). A Pauli channel's exclusive outcomes become
|
||||
//! independent X, Y and Z flips at the rates that give the same law, as a
|
||||
//! depolarizing channel's do, a rate above 1/2 among them when the law needs
|
||||
//! one. A channel with no such rates (`(0.1, 0.1, 0)`: its Z would need a rate
|
||||
//! below zero) is refused with its line, since a detector error model of
|
||||
//! independent mechanisms cannot state it either. The qubit a correlated
|
||||
//! error is carried on is numbered past the text's own, so a text may not
|
||||
//! name qubit 4294967295.
|
||||
//!
|
||||
//! An instruction outside that set is an error, not a skipped line.
|
||||
//! [`ErrorModel::to_dem_text`] writes a model with everything it holds
|
||||
//! (coordinates, tags, each decomposed mechanism's parts joined by `^`);
|
||||
|
|
@ -158,6 +172,10 @@ impl core::fmt::Display for FrameError {
|
|||
/// The most operations a circuit may unroll to.
|
||||
pub const MAX_OPS: usize = 50_000_000;
|
||||
|
||||
/// While a circuit is read: the qubit a correlated error's flip is carried on,
|
||||
/// numbered past the text's own once they are all known.
|
||||
const CARRIER: u32 = u32::MAX;
|
||||
|
||||
/// Parse circuit text, unrolling `REPEAT` blocks and resolving `rec[-k]`.
|
||||
pub fn parse(text: &str) -> Result<Circuit, FrameError> {
|
||||
let prog = crate::quantum_qec_text::parse_circuit(text).map_err(|e| FrameError::Parse { line: e.line, message: e.message })?;
|
||||
|
|
@ -170,9 +188,74 @@ pub fn lower(prog: &crate::quantum_qec_text::CircuitText) -> Result<Circuit, Fra
|
|||
let mut c = Circuit::default();
|
||||
let mut shift: Vec<f64> = Vec::new();
|
||||
lower_items(&prog.items, &mut c, &mut shift)?;
|
||||
let carrier = c.qubits;
|
||||
let mut carried = false;
|
||||
for op in &mut c.ops {
|
||||
if let Op::Noise1 { q, .. } | Op::Reset { q, .. } | Op::Cx(q, _) | Op::Cz(q, _) = op
|
||||
&& *q == CARRIER
|
||||
{
|
||||
*q = carrier;
|
||||
carried = true;
|
||||
}
|
||||
}
|
||||
if carried {
|
||||
// The text's qubits leave no index for it (4294967294 named).
|
||||
c.qubits = c.qubits.checked_add(1).ok_or_else(|| FrameError::Parse {
|
||||
line: first_line(&prog.items, &["E", "CORRELATED_ERROR"]).unwrap_or(0),
|
||||
message: format!("a correlated error is carried on a qubit past the text's last, and qubit {} leaves none", CARRIER - 1),
|
||||
})?;
|
||||
}
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
/// The line of the first instruction named one of `names`, in a block too.
|
||||
fn first_line(items: &[crate::quantum_qec_text::Item], names: &[&str]) -> Option<usize> {
|
||||
use crate::quantum_qec_text::Item;
|
||||
items.iter().find_map(|it| match it {
|
||||
Item::Op(i) => names.contains(&i.name.as_str()).then_some(i.line),
|
||||
Item::Repeat { body, .. } => first_line(body, names),
|
||||
})
|
||||
}
|
||||
|
||||
/// The rates of independent X, Y and Z flips with the law of a channel whose
|
||||
/// outcomes X, Y and Z are exclusive, at `px`, `py` and `pz`. The three
|
||||
/// parities of a flip each multiply over independent channels and add over
|
||||
/// exclusive ones: with `u = 1 − 2q` for each rate `q`, `ux·uy = a`, `uy·uz =
|
||||
/// b` and `ux·uz = c`, where `a = 1 − 2(px + py)`, `b = 1 − 2(py + pz)` and
|
||||
/// `c = 1 − 2(px + pz)`. With none of them zero, `ux² = ac/b`, `uy² = ab/c`
|
||||
/// and `uz² = bc/a`: rates exist when `abc > 0` and no square passes 1, and of
|
||||
/// the two signs they take, the smaller rates are given. A parity of zero
|
||||
/// makes a rate 1/2: with one nonzero, the two rates it is the product of
|
||||
/// give it and the third is 1/2; with none, each is (the channel (1/4, 1/4,
|
||||
/// 1/4), which is `DEPOLARIZE1(0.75)`). A rate may pass 1/2, a flip more
|
||||
/// likely than not. A depolarizing channel is the case `px = py = pz`. None
|
||||
/// when no such rates exist: two parities nonzero and one zero, or a square
|
||||
/// past 1.
|
||||
fn pauli_channel_rates(px: f64, py: f64, pz: f64) -> Option<[f64; 3]> {
|
||||
if [px, py, pz].iter().filter(|&&p| p > 0.0).count() <= 1 {
|
||||
return Some([px, py, pz]);
|
||||
}
|
||||
let (a, b, c) = (1.0 - 2.0 * (px + py), 1.0 - 2.0 * (py + pz), 1.0 - 2.0 * (px + pz));
|
||||
let u = match (a != 0.0, b != 0.0, c != 0.0) {
|
||||
(true, true, true) => {
|
||||
// abc > 0: an even number of them below zero.
|
||||
if ([a, b, c].iter().filter(|&&x| x < 0.0).count()) % 2 == 1 {
|
||||
return None;
|
||||
}
|
||||
// At most 1, up to the rounding of the products.
|
||||
let size = |square: f64| (square <= 1.0 + 8.0 * f64::EPSILON).then(|| square.min(1.0).sqrt());
|
||||
let u = [size(a * c / b)?.copysign(a), size(a * b / c)?, size(b * c / a)?.copysign(b)];
|
||||
if u.iter().sum::<f64>() >= 0.0 { u } else { u.map(|x| -x) }
|
||||
}
|
||||
(false, false, false) => [0.0; 3],
|
||||
(true, false, false) => [a, 1.0, 0.0],
|
||||
(false, true, false) => [0.0, 1.0, b],
|
||||
(false, false, true) => [c, 0.0, 1.0],
|
||||
_ => return None,
|
||||
};
|
||||
Some(u.map(|x| ((1.0 - x) / 2.0).clamp(0.0, 1.0)))
|
||||
}
|
||||
|
||||
fn lower_items(items: &[crate::quantum_qec_text::Item], c: &mut Circuit, shift: &mut Vec<f64>) -> Result<(), FrameError> {
|
||||
use crate::quantum_qec_text::Item;
|
||||
for it in items {
|
||||
|
|
@ -251,6 +334,10 @@ fn lower_instruction(i: &crate::quantum_qec_text::Instruction, c: &mut Circuit,
|
|||
}
|
||||
},
|
||||
};
|
||||
// The carrier's index is not the text's to name, and no qubit past it is.
|
||||
if i.targets.iter().any(|t| matches!(*t, Target::Qubit { q, .. } | Target::Pauli { q, .. } if q == CARRIER)) {
|
||||
return Err(err(&format!("qubit {CARRIER} is past the last qubit a circuit may name")));
|
||||
}
|
||||
let before = c.ops.len();
|
||||
match name {
|
||||
"TICK" | "QUBIT_COORDS" => {}
|
||||
|
|
@ -292,6 +379,18 @@ fn lower_instruction(i: &crate::quantum_qec_text::Instruction, c: &mut Circuit,
|
|||
_ => Op::SqrtX(q),
|
||||
}));
|
||||
}
|
||||
// X → Y → Z → X: S then H on a frame. Its inverse: H then S, as S† is S
|
||||
// on a frame.
|
||||
"C_XYZ" | "C_ZYX" => {
|
||||
if !nums.is_empty() {
|
||||
return Err(err(&format!("{name} takes no arguments")));
|
||||
}
|
||||
let q = qubits(false)?;
|
||||
touch(c, &q);
|
||||
for q in q {
|
||||
c.ops.extend(if name == "C_XYZ" { [Op::S(q), Op::H(q)] } else { [Op::H(q), Op::S(q)] });
|
||||
}
|
||||
}
|
||||
"CX" | "CNOT" | "ZCX" | "CZ" | "ZCZ" | "SWAP" => {
|
||||
let p = pairs()?;
|
||||
for &(a, b) in &p {
|
||||
|
|
@ -318,6 +417,52 @@ fn lower_instruction(i: &crate::quantum_qec_text::Instruction, c: &mut Circuit,
|
|||
touch(c, &q);
|
||||
c.ops.extend(q.into_iter().map(|q| Op::Noise1 { kind, p, q }));
|
||||
}
|
||||
"PAULI_CHANNEL_1" => {
|
||||
if nums.len() != 3 {
|
||||
return Err(err("PAULI_CHANNEL_1 takes three probabilities: of X, of Y and of Z"));
|
||||
}
|
||||
let (px, py, pz) = (prob(0)?, prob(1)?, prob(2)?);
|
||||
if px + py + pz > 1.0 + 8.0 * f64::EPSILON {
|
||||
return Err(FrameError::BadProbability(px + py + pz));
|
||||
}
|
||||
let rates = pauli_channel_rates(px, py, pz).ok_or_else(|| err(&format!("PAULI_CHANNEL_1({px}, {py}, {pz}): its exclusive outcomes have no form as independent X, Y and Z flips, which this simulator and a detector error model both need")))?;
|
||||
let q = qubits(false)?;
|
||||
touch(c, &q);
|
||||
for q in q {
|
||||
for (kind, p) in [Noise1::X, Noise1::Y, Noise1::Z].into_iter().zip(rates) {
|
||||
if p > 0.0 {
|
||||
c.ops.push(Op::Noise1 { kind, p, q });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The flip on a qubit of its own, handed on by `CX` (an X on the
|
||||
// target) and `CZ` (a Z), then reset: every target flips together, in
|
||||
// the frames forward and in the sensitivities backward.
|
||||
"E" | "CORRELATED_ERROR" => {
|
||||
if nums.len() != 1 {
|
||||
return Err(err(&format!("{name} takes one probability")));
|
||||
}
|
||||
let p = prob(0)?;
|
||||
let mut handed = Vec::with_capacity(i.targets.len());
|
||||
for t in &i.targets {
|
||||
match *t {
|
||||
Target::Pauli { pauli, q, inverted: false } => {
|
||||
touch(c, &[q]);
|
||||
if pauli != 'Z' {
|
||||
handed.push(Op::Cx(CARRIER, q));
|
||||
}
|
||||
if pauli != 'X' {
|
||||
handed.push(Op::Cz(CARRIER, q));
|
||||
}
|
||||
}
|
||||
_ => return Err(err(&format!("{name} takes Pauli targets, like X1 Y2 Z3"))),
|
||||
}
|
||||
}
|
||||
c.ops.push(Op::Noise1 { kind: Noise1::X, p, q: CARRIER });
|
||||
c.ops.extend(handed);
|
||||
c.ops.push(Op::Reset { basis: Basis::Z, q: CARRIER });
|
||||
}
|
||||
"DEPOLARIZE2" => {
|
||||
let p = prob(0)?;
|
||||
if p > 15.0 / 16.0 {
|
||||
|
|
@ -1420,6 +1565,226 @@ mod tests {
|
|||
assert_eq!(parse_dem(reference).unwrap().to_text(), reference);
|
||||
}
|
||||
|
||||
/// Each outcome of a Pauli target of an observable, as the frame sees it:
|
||||
/// the net flip of a noise instruction on one qubit, over many shots.
|
||||
fn net_flips(noise: &str, shots: usize) -> [f64; 4] {
|
||||
// Z and X parities read through two observables on one qubit: in |0⟩ a
|
||||
// record reads the X part of the flip, and the Z part reaches a second
|
||||
// record through H on a copy in |+⟩ of the same noise's frame.
|
||||
let text = format!("R 0\n{noise} 0\nOBSERVABLE_INCLUDE(0) Z0\n");
|
||||
let c = parse(&text).unwrap();
|
||||
let s = sample(&c, shots, 3);
|
||||
let x = (0..shots).filter(|&i| s.flips(i) & 1 == 1).count() as f64 / shots as f64;
|
||||
let text = format!("RX 0\n{noise} 0\nOBSERVABLE_INCLUDE(0) X0\n");
|
||||
let c = parse(&text).unwrap();
|
||||
let s = sample(&c, shots, 4);
|
||||
let z = (0..shots).filter(|&i| s.flips(i) & 1 == 1).count() as f64 / shots as f64;
|
||||
let text = format!("R 0\nH 0\nS 0\n{noise} 0\nOBSERVABLE_INCLUDE(0) Y0\n");
|
||||
let c = parse(&text).unwrap();
|
||||
let s = sample(&c, shots, 5);
|
||||
let y = (0..shots).filter(|&i| s.flips(i) & 1 == 1).count() as f64 / shots as f64;
|
||||
[x, z, y, 0.0]
|
||||
}
|
||||
|
||||
/// Every channel whose exclusive law independent flips can give is taken:
|
||||
/// a rate above 1/2 among them, or a parity of zero, as in (0.25, 0.25,
|
||||
/// 0.25), the depolarizing channel DEPOLARIZE1(0.75) also is. Its three
|
||||
/// parities are the channel's, exactly.
|
||||
#[test]
|
||||
fn a_pauli_channel_with_independent_rates_above_one_half_is_taken() {
|
||||
for (px, py, pz) in [(0.6, 0.3, 0.05), (0.7, 0.1, 0.1), (0.01, 0.01, 0.9), (0.25, 0.25, 0.25), (0.5, 0.5, 0.0), (0.1, 0.1, 0.4)] {
|
||||
let [qx, qy, qz] = pauli_channel_rates(px, py, pz).unwrap_or_else(|| panic!("({px}, {py}, {pz}) refused"));
|
||||
for q in [qx, qy, qz] {
|
||||
assert!((0.0..=1.0).contains(&q), "({px}, {py}, {pz}): rate {q}");
|
||||
}
|
||||
let f = |q: f64| 1.0 - 2.0 * q;
|
||||
for (got, want) in [(f(qx) * f(qy), 1.0 - 2.0 * (px + py)), (f(qy) * f(qz), 1.0 - 2.0 * (py + pz)), (f(qx) * f(qz), 1.0 - 2.0 * (px + pz))] {
|
||||
assert!((got - want).abs() < 1e-14, "({px}, {py}, {pz}): {got} against {want}");
|
||||
}
|
||||
assert!(parse(&format!("R 0\nPAULI_CHANNEL_1({px}, {py}, {pz}) 0\nM 0\nDETECTOR rec[-1]\n")).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
/// The qubit a correlated error is carried on is no qubit the text may
|
||||
/// name: noise on it would reach the error's targets, or be reset away.
|
||||
#[test]
|
||||
fn the_carrier_s_index_is_refused_in_text() {
|
||||
for text in [
|
||||
"R 0\nX_ERROR(0.1) 4294967295\nM 0\nDETECTOR rec[-1]\n",
|
||||
"R 1\nX_ERROR(0.1) 4294967295\nE(0.2) X1\nM 1\nDETECTOR rec[-1]\n",
|
||||
"R 0 1\nX_ERROR(0.3) 4294967295\nE(0.01) X1\nCX 4294967295 0\nM 0\nDETECTOR rec[-1]\n",
|
||||
"R 0\nE(0.1) X4294967295\nM 0\nDETECTOR rec[-1]\n",
|
||||
] {
|
||||
let e = parse(text).map(|c| c.qubits).unwrap_err();
|
||||
assert!(e.to_string().contains("4294967295"), "{text}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A text whose qubits reach 4294967294 leaves the carrier no index: a
|
||||
/// circuit with a correlated error is refused at the error's line.
|
||||
#[test]
|
||||
fn a_correlated_error_with_no_index_left_for_its_carrier_is_refused() {
|
||||
for (text, line) in [("R 0\nE(0.1) X1\nX_ERROR(0.1) 4294967294\nM 0\nDETECTOR rec[-1]\n", 2), ("R 0\nX 4294967294\nREPEAT 2 {\n CORRELATED_ERROR(0.1) Z0\n}\nM 0\n", 4)] {
|
||||
let e = parse(text).map(|c| c.qubits).unwrap_err();
|
||||
assert!(e.to_string().starts_with(&format!("line {line}:")) && e.to_string().contains("4294967294"), "{text}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The instructions this reader added are in the page that lists the text
|
||||
/// forms and in the conformance set that says it holds each one.
|
||||
#[test]
|
||||
fn the_qec_page_and_conformance_set_name_these_forms() {
|
||||
let page = include_str!("../docs/qec.md");
|
||||
let set = include_str!("../../quantum-conformance/qec-text/README.md");
|
||||
for form in ["C_XYZ", "C_ZYX", "CORRELATED_ERROR", "PAULI_CHANNEL_1"] {
|
||||
assert!(page.contains(form) && set.contains(form), "{form}");
|
||||
}
|
||||
assert!(page.contains("one qubit more") && page.contains("PAULI_CHANNEL_1(0.1, 0.1, 0)"));
|
||||
}
|
||||
|
||||
/// `C_XYZ` and `C_ZYX` take no arguments, and are refused with one, as the
|
||||
/// reference tool refuses them.
|
||||
#[test]
|
||||
fn the_cycling_gates_take_no_arguments() {
|
||||
for text in ["R 0\nC_XYZ(0.1) 0\nM 0\n", "R 0\nC_ZYX(0.5, 7) 0\nM 0\n"] {
|
||||
let e = parse(text).map(|c| c.qubits).unwrap_err();
|
||||
assert!(e.to_string().starts_with("line 2:"), "{text}: {e}");
|
||||
}
|
||||
assert!(parse("R 0\nC_XYZ 0\nC_ZYX 0\nM 0\n").is_ok());
|
||||
}
|
||||
|
||||
/// The rates of independent flips give the exclusive channel's law, to the
|
||||
/// rounding: its three parities, exactly. A depolarizing channel is the
|
||||
/// case of equal probabilities, and a channel with no such rates has none.
|
||||
#[test]
|
||||
fn a_pauli_channel_s_rates_have_its_law() {
|
||||
for (px, py, pz) in [(0.01, 0.02, 0.03), (0.2, 0.1, 0.05), (0.001, 0.001, 0.01), (0.1, 0.0, 0.0), (0.0, 0.0, 0.0), (0.2, 0.2, 0.2)] {
|
||||
let [qx, qy, qz] = pauli_channel_rates(px, py, pz).unwrap();
|
||||
let f = |q: f64| 1.0 - 2.0 * q;
|
||||
for (got, want) in [(f(qx) * f(qy), 1.0 - 2.0 * (px + py)), (f(qy) * f(qz), 1.0 - 2.0 * (py + pz)), (f(qx) * f(qz), 1.0 - 2.0 * (px + pz))] {
|
||||
assert!((got - want).abs() < 1e-14, "({px}, {py}, {pz}): {got} against {want}");
|
||||
}
|
||||
}
|
||||
let [q, r, s] = pauli_channel_rates(0.03, 0.03, 0.03).unwrap();
|
||||
assert!((q - independent_rate(0.09, 1)).abs() < 1e-15 && q == r && r == s);
|
||||
assert_eq!(pauli_channel_rates(0.1, 0.1, 0.0), None);
|
||||
// Refused with its line, and not read as no noise.
|
||||
let e = parse("R 0\nPAULI_CHANNEL_1(0.1, 0.1, 0) 0\nM 0\n").unwrap_err();
|
||||
assert!(e.to_string().starts_with("line 2:") && e.to_string().contains("no form as independent"), "{e}");
|
||||
assert!(matches!(parse("PAULI_CHANNEL_1(0.5, 0.4, 0.3) 0\n"), Err(FrameError::BadProbability(_))));
|
||||
assert!(matches!(parse("PAULI_CHANNEL_1(0.1) 0\n"), Err(FrameError::Parse { line: 1, .. })));
|
||||
}
|
||||
|
||||
/// A Pauli channel as a sample sees it: an X target flips under Z and Y, a
|
||||
/// Z target under X and Y, a Y target under X and Z.
|
||||
#[test]
|
||||
fn a_pauli_channel_samples_its_law() {
|
||||
let (px, py, pz) = (0.05, 0.1, 0.15);
|
||||
let [z_flips, x_flips, y_flips, _] = net_flips(&format!("PAULI_CHANNEL_1({px}, {py}, {pz})"), 400_000);
|
||||
for (got, want) in [(z_flips, px + py), (x_flips, py + pz), (y_flips, px + pz)] {
|
||||
assert!((got - want).abs() < 5.0 * (want * (1.0 - want) / 400_000.0).sqrt(), "{got} against {want}");
|
||||
}
|
||||
}
|
||||
|
||||
/// `C_XYZ` sends X to Y, Y to Z and Z to X, and `C_ZYX` undoes it, in the
|
||||
/// frames forward and the sensitivities backward.
|
||||
#[test]
|
||||
fn the_cycling_gates_cycle_the_axes() {
|
||||
// (prepared in, error before the gate, the target after it): an error
|
||||
// flips a target exactly if the gate's image of it anticommutes.
|
||||
for (gate, error, target, flips) in [
|
||||
("C_XYZ", "X_ERROR(0.1)", "Y0", false),
|
||||
("C_XYZ", "X_ERROR(0.1)", "Z0", true),
|
||||
("C_XYZ", "X_ERROR(0.1)", "X0", true),
|
||||
("C_XYZ", "Y_ERROR(0.1)", "Z0", false),
|
||||
("C_XYZ", "Z_ERROR(0.1)", "X0", false),
|
||||
("C_ZYX", "Z_ERROR(0.1)", "Y0", false),
|
||||
("C_ZYX", "Y_ERROR(0.1)", "X0", false),
|
||||
("C_ZYX", "X_ERROR(0.1)", "Z0", false),
|
||||
] {
|
||||
// A state fixed by the target's Pauli, by way of the gate itself.
|
||||
let prep = match target {
|
||||
"Z0" => "R 0",
|
||||
"X0" => "RX 0",
|
||||
_ => "R 0\nH 0\nS 0",
|
||||
};
|
||||
let inverse = if gate == "C_XYZ" { "C_ZYX" } else { "C_XYZ" };
|
||||
let text = format!("{prep}\n{inverse} 0\n{error} 0\n{gate} 0\nOBSERVABLE_INCLUDE(0) {target}\n");
|
||||
let c = parse(&text).unwrap();
|
||||
let m = error_model(&c).unwrap();
|
||||
let got: Vec<u64> = m.mechanisms.iter().map(|x| x.observables).collect();
|
||||
assert_eq!(got, if flips { vec![1] } else { vec![] }, "{text}");
|
||||
let shots = 40_000;
|
||||
let s = sample(&c, shots, 21);
|
||||
let rate = (0..shots).filter(|&i| s.flips(i) & 1 == 1).count() as f64 / shots as f64;
|
||||
assert!((rate - if flips { 0.1 } else { 0.0 }).abs() < 0.01, "{text}: {rate}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A correlated error flips all its targets together, each by its Pauli,
|
||||
/// on the shots it fires and no others: one mechanism with every symptom,
|
||||
/// and two detectors firing on the same shots. The carrier is a qubit past
|
||||
/// the text's own, and an error's tag is its mechanism's.
|
||||
#[test]
|
||||
fn a_correlated_error_flips_its_targets_together() {
|
||||
let text = "R 0 1 2\nE[burst](0.25) X0 Y1\nCORRELATED_ERROR(0.125) X2\nM 0 1 2\nDETECTOR rec[-3]\nDETECTOR rec[-2]\nDETECTOR rec[-1]\n";
|
||||
let c = parse(text).unwrap();
|
||||
assert_eq!((c.qubits, c.measurements), (4, 3));
|
||||
let m = error_model(&c).unwrap();
|
||||
let got: Vec<(Vec<u32>, f64, Option<String>)> = m.mechanisms.iter().map(|x| (x.detectors.clone(), x.probability, x.tag.clone())).collect();
|
||||
assert_eq!(got, vec![(vec![0, 1], 0.25, Some("burst".to_string())), (vec![2], 0.125, None)]);
|
||||
let shots = 200_000;
|
||||
let s = sample(&c, shots, 7);
|
||||
assert!((0..shots).all(|i| s.fired(i).contains(&0) == s.fired(i).contains(&1)), "the two flip together");
|
||||
for (d, want) in [(0, 0.25), (2, 0.125)] {
|
||||
let rate = s.count(d) as f64 / shots as f64;
|
||||
assert!((rate - want).abs() < 5.0 * (want * (1.0 - want) / shots as f64).sqrt(), "D{d}: {rate}");
|
||||
}
|
||||
// Each error draws anew: two in a row merge by the XOR rule, in both.
|
||||
let two = parse("R 0\nE(0.1) X0\nE(0.2) X0\nM 0\nDETECTOR rec[-1]\n").unwrap();
|
||||
let want = 0.1 + 0.2 - 2.0 * 0.1 * 0.2;
|
||||
assert!((error_model(&two).unwrap().mechanisms[0].probability - want).abs() < 1e-15);
|
||||
let rate = sample(&two, shots, 9).count(0) as f64 / shots as f64;
|
||||
assert!((rate - want).abs() < 5.0 * (want * (1.0 - want) / shots as f64).sqrt(), "{rate}");
|
||||
for bad in ["E(0.1) 0\n", "E(0.1, 0.2) X0\n", "E(0.1) !X0\n", "E X0\n"] {
|
||||
assert!(matches!(parse(bad), Err(FrameError::Parse { line: 1, .. })), "{bad}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The reference generator's colour-code memory, whose cycle turns the axes
|
||||
/// with `C_XYZ`, reads and reaches that tool's model of it, mechanism for
|
||||
/// mechanism (`tests/data/color_d3.dem`: flattened, like symptoms merged).
|
||||
#[test]
|
||||
fn the_reference_colour_code_has_the_reference_s_model() {
|
||||
use std::collections::BTreeMap;
|
||||
let symptoms = |line: &str| -> Option<(String, f64)> {
|
||||
let rest = line.strip_prefix("error(")?;
|
||||
let (p, s) = rest.split_once(')')?;
|
||||
Some((s.trim().to_string(), p.parse().unwrap()))
|
||||
};
|
||||
let text = include_str!("../tests/data/color_d3.circuit");
|
||||
assert!(text.contains("C_XYZ"));
|
||||
let c = parse(text).unwrap();
|
||||
let ours: BTreeMap<String, f64> = error_model(&c).unwrap().to_text().lines().filter_map(symptoms).collect();
|
||||
let theirs: BTreeMap<String, f64> = include_str!("../tests/data/color_d3.dem").lines().filter_map(symptoms).collect();
|
||||
assert_eq!(ours.keys().collect::<Vec<_>>(), theirs.keys().collect::<Vec<_>>());
|
||||
for (k, q) in &theirs {
|
||||
assert!((ours[k] / q - 1.0).abs() < 1e-9, "{k}: {} against {q}", ours[k]);
|
||||
}
|
||||
// The rates the sampler gives follow the model, at the detectors they reach.
|
||||
let m = error_model(&c).unwrap();
|
||||
let shots = 200_000;
|
||||
let s = sample(&c, shots, 13);
|
||||
for d in 0..m.detectors {
|
||||
let mut q = 0.0;
|
||||
for mech in m.mechanisms.iter().filter(|x| x.detectors.contains(&d)) {
|
||||
q = q + mech.probability - 2.0 * q * mech.probability;
|
||||
}
|
||||
let rate = s.count(d as usize) as f64 / shots as f64;
|
||||
assert!((rate - q).abs() < 6.0 * (q * (1.0 - q) / shots as f64).sqrt().max(1e-4), "D{d}: {rate} against {q}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_surface_code_model_is_graphlike() {
|
||||
for d in [3, 5] {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ pub enum MpsError {
|
|||
BadGate(usize),
|
||||
/// A bit string or Pauli string that does not fit the chain.
|
||||
BadQuery(String),
|
||||
/// The work passed the budget [`Chain::run_within`] holds a run to.
|
||||
OverBudget,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MpsError {
|
||||
|
|
@ -86,12 +88,23 @@ impl std::fmt::Display for MpsError {
|
|||
MpsError::TooManyQubits(n) => write!(f, "{n} qubits; a chain holds at most {MAX_QUBITS}"),
|
||||
MpsError::BadGate(i) => write!(f, "gate {i} is not a one- or two-qubit gate on the chain"),
|
||||
MpsError::BadQuery(why) => write!(f, "{why}"),
|
||||
MpsError::OverBudget => write!(f, "the work passed the run's budget"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for MpsError {}
|
||||
|
||||
/// Why [`Chain::run_within`] stopped.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MpsRunError {
|
||||
Chain(MpsError),
|
||||
/// The work passed the budget in gate `gate` (from 1) of `gates`, at the
|
||||
/// two-site update that passed it, the bond having reached
|
||||
/// `max_bond_used`.
|
||||
OutOfWork { gate: usize, gates: usize, work: u64, max_bond_used: usize },
|
||||
}
|
||||
|
||||
/// What a run's truncations cost.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct MpsReport {
|
||||
|
|
@ -125,6 +138,8 @@ pub struct Chain {
|
|||
fidelity: f64,
|
||||
truncations: u64,
|
||||
updates: u64,
|
||||
work: u64,
|
||||
budget: u64,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -179,6 +194,53 @@ fn qr(cols: &[Vec<C>], rows: usize) -> (Vec<Vec<C>>, Vec<Vec<C>>) {
|
|||
(q, r)
|
||||
}
|
||||
|
||||
/// An upper bound on [`Chain::work`] for a run of gates, at bond `chi` at
|
||||
/// most: the walk [`Chain::apply`] makes, each two-site update at `(2χ)³`,
|
||||
/// each move of the orthogonality center at `4χ³`, each one-qubit gate at
|
||||
/// `4χ²`. What a job's work is planned in. Each gate is counted in a few
|
||||
/// operations, however far apart its qubits are.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WorkBound {
|
||||
chi: u64,
|
||||
center: u64,
|
||||
work: u64,
|
||||
}
|
||||
|
||||
impl WorkBound {
|
||||
/// A run from `|0…0⟩`, its center on the first site.
|
||||
pub fn new(chi: u64) -> WorkBound {
|
||||
WorkBound { chi, center: 0, work: 0 }
|
||||
}
|
||||
|
||||
/// One gate on these qubits; a gate on more than two counts nothing (the
|
||||
/// chain refuses it).
|
||||
pub fn gate(&mut self, qubits: &[u32]) {
|
||||
let chi = self.chi;
|
||||
match *qubits {
|
||||
[_] => self.work = self.work.saturating_add(chi.saturating_mul(chi).saturating_mul(4)),
|
||||
[a, b] if a != b => {
|
||||
// `hi − lo` swaps down, the gate, and as many swaps back: the
|
||||
// first update at `hi − 1` (`lo` when they are neighbours),
|
||||
// each next one down two moves on, each one back up none, and
|
||||
// the center left on `hi`.
|
||||
let (lo, hi) = (a.min(b) as u64, a.max(b) as u64);
|
||||
let d = hi - lo;
|
||||
let moves = self.center.abs_diff(hi - 1).saturating_add(2 * (d - 1));
|
||||
let update = (2 * chi).saturating_pow(3);
|
||||
let shift = chi.saturating_pow(3).saturating_mul(4);
|
||||
self.work = self.work.saturating_add((2 * d - 1).saturating_mul(update)).saturating_add(moves.saturating_mul(shift));
|
||||
self.center = hi;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// The bound so far.
|
||||
pub fn work(&self) -> u64 {
|
||||
self.work
|
||||
}
|
||||
}
|
||||
|
||||
impl Chain {
|
||||
/// `|0…0⟩` on `n` qubits.
|
||||
pub fn new(n: u32, cfg: MpsConfig) -> Result<Chain, MpsError> {
|
||||
|
|
@ -198,6 +260,8 @@ impl Chain {
|
|||
fidelity: 1.0,
|
||||
truncations: 0,
|
||||
updates: 0,
|
||||
work: 0,
|
||||
budget: u64::MAX,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -210,6 +274,32 @@ impl Chain {
|
|||
Ok(c)
|
||||
}
|
||||
|
||||
/// [`Chain::run`], holding the run to `budget` units of work ([`Chain::work`]),
|
||||
/// counted on the bonds it actually holds. It stops at the two-site update
|
||||
/// or one-qubit gate that passes the budget, inside a gate that crosses
|
||||
/// the chain too; the meter changes no number of a run that finishes.
|
||||
pub fn run_within(n: u32, gates: &[Gate], cfg: MpsConfig, budget: u64) -> Result<Chain, MpsRunError> {
|
||||
let mut c = Chain::new(n, cfg).map_err(MpsRunError::Chain)?;
|
||||
c.budget = budget;
|
||||
for (i, g) in gates.iter().enumerate() {
|
||||
match c.apply(g) {
|
||||
Ok(()) => {}
|
||||
Err(MpsError::OverBudget) => return Err(MpsRunError::OutOfWork { gate: i + 1, gates: gates.len(), work: c.work, max_bond_used: c.max_bond_used }),
|
||||
Err(_) => return Err(MpsRunError::Chain(MpsError::BadGate(i))),
|
||||
}
|
||||
}
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
/// The work so far: each two-site update at `(2χ)³`, `χ` the larger of the
|
||||
/// two bonds it spans; each move of the orthogonality center at the
|
||||
/// products its QR and its multiplication take (at most `4χ³`); each
|
||||
/// one-qubit gate at `4·dl·dr`. The unit a job's work is planned in
|
||||
/// ([`WorkBound`]), with `χ` there the bond asked for.
|
||||
pub fn work(&self) -> u64 {
|
||||
self.work
|
||||
}
|
||||
|
||||
pub fn qubits(&self) -> u32 {
|
||||
self.n as u32
|
||||
}
|
||||
|
|
@ -235,6 +325,8 @@ impl Chain {
|
|||
let cols: Vec<Vec<C>> = (0..dr).map(|r| (0..rows).map(|ls| self.a[i][ls * dr + r]).collect()).collect();
|
||||
let (q, rm) = qr(&cols, rows);
|
||||
let k = q.len();
|
||||
let ndr = self.dr[i + 1];
|
||||
self.charge((rows * dr * k + k * dr * 2 * ndr) as u64);
|
||||
let mut na = vec![C::ZERO; rows * k];
|
||||
for (c, qc) in q.iter().enumerate() {
|
||||
for ls in 0..rows {
|
||||
|
|
@ -242,7 +334,7 @@ impl Chain {
|
|||
}
|
||||
}
|
||||
// A[i+1] ← R · A[i+1].
|
||||
let (ndr, nb_old) = (self.dr[i + 1], &self.a[i + 1]);
|
||||
let nb_old = &self.a[i + 1];
|
||||
let mut nb = vec![C::ZERO; k * 2 * ndr];
|
||||
for c in 0..k {
|
||||
for j in 0..dr {
|
||||
|
|
@ -275,6 +367,8 @@ impl Chain {
|
|||
let cols: Vec<Vec<C>> = (0..dl).map(|l| (0..width).map(|sr| self.a[i][l * width + sr].conj()).collect()).collect();
|
||||
let (q, rm) = qr(&cols, width);
|
||||
let k = q.len();
|
||||
let (pdl, pdr) = (self.dl[i - 1], self.dr[i - 1]);
|
||||
self.charge((width * dl * k + pdl * 2 * k * pdr) as u64);
|
||||
// A[i] ← Q† as (k, 2, dr).
|
||||
let mut na = vec![C::ZERO; k * width];
|
||||
for (c, qc) in q.iter().enumerate() {
|
||||
|
|
@ -283,8 +377,7 @@ impl Chain {
|
|||
}
|
||||
}
|
||||
// A[i−1] ← A[i−1] · R†.
|
||||
let (pdl, old) = (self.dl[i - 1], &self.a[i - 1]);
|
||||
let pdr = self.dr[i - 1];
|
||||
let old = &self.a[i - 1];
|
||||
let mut nb = vec![C::ZERO; pdl * 2 * k];
|
||||
for l in 0..pdl {
|
||||
for s in 0..2 {
|
||||
|
|
@ -313,8 +406,19 @@ impl Chain {
|
|||
}
|
||||
}
|
||||
|
||||
/// Adds `units` to the work.
|
||||
fn charge(&mut self, units: u64) {
|
||||
self.work = self.work.saturating_add(units);
|
||||
}
|
||||
|
||||
/// Whether the work is still within the budget.
|
||||
fn within(&self) -> Result<(), MpsError> {
|
||||
if self.work > self.budget { Err(MpsError::OverBudget) } else { Ok(()) }
|
||||
}
|
||||
|
||||
fn apply1(&mut self, i: usize, g: &[C]) {
|
||||
let (dl, dr) = (self.dl[i], self.dr[i]);
|
||||
self.charge((4 * dl * dr) as u64);
|
||||
let a = &mut self.a[i];
|
||||
for l in 0..dl {
|
||||
for r in 0..dr {
|
||||
|
|
@ -328,6 +432,8 @@ impl Chain {
|
|||
/// A two-qubit gate on sites `i`, `i + 1`, its 4×4 matrix with site `i`
|
||||
/// the more significant bit; the center ends on `i + 1`.
|
||||
fn apply2_adjacent(&mut self, i: usize, g: &[C]) {
|
||||
let chi = self.dl[i].max(self.dr[i + 1]) as u64;
|
||||
self.charge((2 * chi).saturating_pow(3));
|
||||
self.move_center(i);
|
||||
let (dl, dc, dr) = (self.dl[i], self.dr[i], self.dr[i + 1]);
|
||||
let (rows, cols) = (dl * 2, 2 * dr);
|
||||
|
|
@ -455,13 +561,14 @@ impl Chain {
|
|||
}
|
||||
|
||||
/// Applies a one- or two-qubit gate (its matrix with `qubits[0]` the
|
||||
/// most significant bit).
|
||||
/// most significant bit). Under a budget ([`Chain::run_within`]) it stops
|
||||
/// at the update that passes it, the chain then part way through the gate.
|
||||
pub fn apply(&mut self, g: &Gate) -> Result<(), MpsError> {
|
||||
let n = self.n;
|
||||
match *g.qubits.as_slice() {
|
||||
[q] if (q as usize) < n && g.matrix.len() == 4 => {
|
||||
self.apply1(q as usize, &g.matrix);
|
||||
Ok(())
|
||||
self.within()
|
||||
}
|
||||
[a, b] if a != b && (a as usize) < n && (b as usize) < n && g.matrix.len() == 16 => {
|
||||
let (a, b) = (a as usize, b as usize);
|
||||
|
|
@ -474,13 +581,17 @@ impl Chain {
|
|||
};
|
||||
let (lo, hi) = (a.min(b), a.max(b));
|
||||
let swap = Gate::swap(0, 1).matrix;
|
||||
// Bring `hi` down next to `lo`, apply, and take it back.
|
||||
// Bring `hi` down next to `lo`, apply, and take it back. (The
|
||||
// walk [`WorkBound`] counts.)
|
||||
for k in (lo + 1..hi).rev() {
|
||||
self.apply2_adjacent(k, &swap);
|
||||
self.within()?;
|
||||
}
|
||||
self.apply2_adjacent(lo, &m);
|
||||
self.within()?;
|
||||
for k in lo + 1..hi {
|
||||
self.apply2_adjacent(k, &swap);
|
||||
self.within()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -633,6 +744,129 @@ fn splitmix(s: &mut u64) -> u64 {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Rotations and a brickwork of cx on `n` qubits, `layers` deep: enough to
|
||||
/// fill the chain to the bond its cfg allows in the middle.
|
||||
fn saturating(n: u32, layers: usize) -> Vec<Gate> {
|
||||
let mut gates = Vec::new();
|
||||
for layer in 0..layers {
|
||||
for q in 0..n {
|
||||
gates.push(Gate::rx(q, 0.3 + 0.1 * q as f64 + 0.07 * layer as f64));
|
||||
gates.push(Gate::ry(q, 0.5 + 0.03 * q as f64));
|
||||
}
|
||||
for q in (layer as u32 % 2..n - 1).step_by(2) {
|
||||
gates.push(Gate::cx(q, q + 1));
|
||||
}
|
||||
}
|
||||
gates
|
||||
}
|
||||
|
||||
/// Moving the orthogonality center across a full chain is QR at the bonds
|
||||
/// it holds, and the meter charges it: a gate at each end of a saturated
|
||||
/// chain moves the center across it twice. Charged only at the ends' own
|
||||
/// bonds (at most 4 there), the pair cost 1024 units and took the time of
|
||||
/// ~2(n − 3) moves at the middle's bond.
|
||||
#[test]
|
||||
fn the_meter_charges_each_move_of_the_center() {
|
||||
let n = 24;
|
||||
let cfg = MpsConfig { max_bond: 32, cutoff: 1e-12 };
|
||||
let mut c = Chain::run(n, &saturating(n, 12), cfg).unwrap();
|
||||
assert!(c.report().max_bond_used >= 16, "{:?}", c.report());
|
||||
let before = c.work();
|
||||
c.apply(&Gate::cx(0, 1)).unwrap();
|
||||
c.apply(&Gate::cx(n - 2, n - 1)).unwrap();
|
||||
let hop = c.work() - before;
|
||||
assert!(hop > 20 * 16u64.pow(3), "{hop} units for two crossings of a chain at bond 16 or more");
|
||||
}
|
||||
|
||||
/// An update is charged at the bond it spans: with the center already in
|
||||
/// place, what a gate adds to the meter is `(2χ)³` of the bonds it finds,
|
||||
/// not of a bond of one.
|
||||
#[test]
|
||||
fn an_update_is_priced_at_the_bond_it_spans() {
|
||||
let n = 16u32;
|
||||
let mut c = Chain::run(n, &saturating(n, 10), MpsConfig { max_bond: 32, cutoff: 1e-12 }).unwrap();
|
||||
let m = n as usize / 2;
|
||||
c.apply(&Gate::cx(m as u32 - 1, m as u32)).unwrap();
|
||||
assert_eq!(c.center, m);
|
||||
let chi = c.dl[m].max(c.dr[m + 1]) as u64;
|
||||
assert!(chi >= 8, "{:?}", c.report());
|
||||
let before = c.work();
|
||||
c.apply(&Gate::cx(m as u32, m as u32 + 1)).unwrap();
|
||||
assert_eq!(c.work() - before, (2 * chi).pow(3));
|
||||
}
|
||||
|
||||
/// The walk a plan bounds the work on holds the meter from above, on
|
||||
/// circuits whose gates jump across the chain and at bonds it fills.
|
||||
#[test]
|
||||
fn the_bound_holds_the_meter() {
|
||||
for (n, seed) in [(10u32, 1u64), (16, 2), (24, 3)] {
|
||||
for chi in [2usize, 8, 32] {
|
||||
let mut gates = random_circuit(n, 5, seed);
|
||||
gates.extend([Gate::cx(0, n - 1), Gate::h(n / 2), Gate::cx(n - 1, 1), Gate::cx(n / 2, n / 2 - 1)]);
|
||||
let c = Chain::run(n, &gates, MpsConfig { max_bond: chi, cutoff: 1e-12 }).unwrap();
|
||||
let mut bound = WorkBound::new(chi as u64);
|
||||
for g in &gates {
|
||||
bound.gate(&g.qubits);
|
||||
}
|
||||
assert!(c.work() > 0 && c.work() <= bound.work(), "{n} qubits at bond {chi}: run {} over bound {}", c.work(), bound.work());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The budget is held at each two-site update, not after a whole gate: a
|
||||
/// gate that crosses the chain stops at the update that passes it.
|
||||
#[test]
|
||||
fn a_far_gate_stops_at_the_update_that_passes_the_budget() {
|
||||
let n = 24u32;
|
||||
let chi = 32u64;
|
||||
let cfg = MpsConfig { max_bond: chi as usize, cutoff: 1e-12 };
|
||||
let mut gates = saturating(n, 12);
|
||||
let base = Chain::run(n, &gates, cfg).unwrap().work();
|
||||
gates.push(Gate::cx(0, n - 1));
|
||||
let budget = base + 1;
|
||||
match Chain::run_within(n, &gates, cfg, budget) {
|
||||
Err(MpsRunError::OutOfWork { work, .. }) => {
|
||||
// One update, and the moves of the center before it, at the bond.
|
||||
let one = (2 * chi).pow(3) + n as u64 * 4 * chi.pow(3);
|
||||
assert!(work - budget <= one, "passed the budget by {} units, more than one update's {one}", work - budget);
|
||||
}
|
||||
other => panic!("{:?}", other.map(|c| c.report())),
|
||||
}
|
||||
}
|
||||
|
||||
/// The meter counts the bonds a run holds and changes no number of it: a
|
||||
/// run that finishes is the run without a meter; one that passes its
|
||||
/// budget stops at the gate that passed it.
|
||||
#[test]
|
||||
fn the_meter_counts_the_bonds_reached_and_changes_nothing() {
|
||||
// The QFT from |0…0⟩ is a product state at every step: each `cp` finds
|
||||
// its other qubit in |0⟩, so it runs at bond 1, far below the bond asked.
|
||||
let n = 24;
|
||||
let mut gates = Vec::new();
|
||||
for q in 0..n {
|
||||
gates.push(Gate::h(q));
|
||||
for t in q + 1..n {
|
||||
gates.push(Gate::cphase(t, q, core::f64::consts::PI / (1u64 << (t - q)) as f64));
|
||||
}
|
||||
}
|
||||
let cfg = MpsConfig { max_bond: 256, cutoff: 1e-12 };
|
||||
let plain = Chain::run(n, &gates, cfg).unwrap();
|
||||
let metered = Chain::run_within(n, &gates, cfg, u64::MAX).unwrap();
|
||||
assert_eq!(plain.report(), metered.report());
|
||||
assert_eq!(plain.amplitude(&[0; 24]).unwrap(), metered.amplitude(&[0; 24]).unwrap());
|
||||
let r = plain.report();
|
||||
assert_eq!(r.max_bond_used, 1);
|
||||
// Below the bound at the bond asked, which is (2·256)³ an update.
|
||||
assert_eq!(plain.work(), metered.work());
|
||||
assert!(plain.work() > 0 && plain.work() < r.updates * 512u64.pow(3) / 100, "{} over {} updates", plain.work(), r.updates);
|
||||
match Chain::run_within(n, &gates, cfg, plain.work() / 2) {
|
||||
Err(MpsRunError::OutOfWork { gate, gates: g, work, max_bond_used }) => {
|
||||
assert!(gate < g && work > plain.work() / 2 && max_bond_used <= r.max_bond_used, "{gate} of {g}: {work}");
|
||||
}
|
||||
other => panic!("{:?}", other.map(|c| c.report())),
|
||||
}
|
||||
}
|
||||
use crate::quantum_sv::StateVector;
|
||||
|
||||
fn random_circuit(n: u32, depth: usize, seed: u64) -> Vec<Gate> {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,11 @@
|
|||
//! as without tiles, so the schedule never changes a bit. A gate of three
|
||||
//! or more qubits on the top qubits, which leaves the direct kernels too
|
||||
//! few blocks to share between threads, runs in tiles too.
|
||||
//! - **Controlled gates.** A one-qubit gate under controls runs as one
|
||||
//! ([`Controlled`], [`StateVector::run_ops`]): only the amplitudes every
|
||||
//! control leaves on are visited, `2^(n − c)` of them under `c` controls, by
|
||||
//! the same expressions as a one-qubit gate. Runs of other gates between such
|
||||
//! gates fuse and tile as above; a run without one is exactly [`StateVector::run`].
|
||||
//! - **Threads.** Every amplitude group is updated independently, so a gate's
|
||||
//! result does not depend on how the work is split. Reductions (norms,
|
||||
//! expectations, sampling) are summed in a fixed number of chunks, each in
|
||||
|
|
@ -86,6 +91,22 @@ pub struct StateVector {
|
|||
pub im: Vec<f64>,
|
||||
}
|
||||
|
||||
/// A one-qubit gate under controls, as one gate: `matrix` (row-major, rows
|
||||
/// outputs) acts on `target` wherever every one of `controls` reads 1.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Controlled {
|
||||
pub controls: Vec<u32>,
|
||||
pub target: u32,
|
||||
pub matrix: [C; 4],
|
||||
}
|
||||
|
||||
/// One gate of a run ([`StateVector::run_ops`]).
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SvOp {
|
||||
Gate(Gate),
|
||||
Controlled(Controlled),
|
||||
}
|
||||
|
||||
/// Run `f` over `re`/`im` split into contiguous slices of whole `unit`s,
|
||||
/// threaded where the target has threads; `f` gets each slice and its offset.
|
||||
/// Each thread owns its slices, so nothing is shared.
|
||||
|
|
@ -617,6 +638,112 @@ impl StateVector {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn check_controlled(&self, g: &Controlled, gi: usize) -> Result<(), SvError> {
|
||||
let mut q = g.controls.clone();
|
||||
q.push(g.target);
|
||||
q.sort_unstable();
|
||||
q.dedup();
|
||||
if q.len() != g.controls.len() + 1 || q.last().is_some_and(|&x| x >= self.n) {
|
||||
return Err(SvError::BadGate(gi));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply one gate under controls.
|
||||
pub fn apply_controlled(&mut self, g: &Controlled) -> Result<(), SvError> {
|
||||
self.check_controlled(g, 0)?;
|
||||
self.controlled_unchecked(g);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply gates in order: each [`Controlled`] as one, and each run of other
|
||||
/// gates between them as [`StateVector::run`] applies it, fused to `width`.
|
||||
pub fn run_ops(&mut self, ops: &[SvOp], width: usize) -> Result<(), SvError> {
|
||||
for (i, op) in ops.iter().enumerate() {
|
||||
match op {
|
||||
SvOp::Gate(g) => self.check(g, i)?,
|
||||
SvOp::Controlled(c) => self.check_controlled(c, i)?,
|
||||
}
|
||||
}
|
||||
let mut run: Vec<Gate> = Vec::new();
|
||||
for op in ops {
|
||||
match op {
|
||||
SvOp::Gate(g) => run.push(g.clone()),
|
||||
SvOp::Controlled(c) => {
|
||||
if !run.is_empty() {
|
||||
self.run(&run, width)?;
|
||||
run.clear();
|
||||
}
|
||||
self.controlled_unchecked(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !run.is_empty() {
|
||||
self.run(&run, width)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Visit each pair the controls leave on, in `2^(n − c − 1)` groups: the
|
||||
/// next free index comes from the last by carrying through the fixed bits,
|
||||
/// and a thread starts from where its share begins. Each pair goes through
|
||||
/// [`pair`] or, for a diagonal gate, the diagonal path's expression, so a
|
||||
/// group's amplitudes do not depend on how the groups are shared.
|
||||
fn controlled_unchecked(&mut self, g: &Controlled) {
|
||||
let n = self.n;
|
||||
let tbit = 1usize << (n - 1 - g.target);
|
||||
let cmask = g.controls.iter().fold(0usize, |m, &q| m | (1usize << (n - 1 - q)));
|
||||
let fixed = cmask | tbit;
|
||||
let mut pos: Vec<u32> = g.controls.iter().chain([&g.target]).map(|&q| n - 1 - q).collect();
|
||||
pos.sort_unstable();
|
||||
let groups = 1usize << (n as usize - pos.len());
|
||||
let m = g.matrix;
|
||||
let (zero, one) = (|c: C| c.re == 0.0 && c.im == 0.0, |c: C| c.re == 1.0 && c.im == 0.0);
|
||||
let diagonal = zero(m[1]) && zero(m[2]) && one(m[0]);
|
||||
let swap = zero(m[0]) && zero(m[3]) && one(m[1]) && one(m[2]);
|
||||
let raw = Raw { re: self.re.as_mut_ptr(), im: self.im.as_mut_ptr() };
|
||||
let walk = |first: usize, last: usize| {
|
||||
let mut x = spread(first, &pos);
|
||||
let c = m[3];
|
||||
for _ in first..last {
|
||||
let (a, b) = (x | cmask, x | fixed);
|
||||
// SAFETY: `a` and `b` are below `2^n`, every bit being one of
|
||||
// the state's. No two groups share an index: a group is named
|
||||
// by its free bits, and each thread walks a range of its own.
|
||||
unsafe {
|
||||
let (re, im) = (raw.re(), raw.im());
|
||||
if diagonal {
|
||||
let (br, bi) = (*re.add(b), *im.add(b));
|
||||
*re.add(b) = c.re * br - c.im * bi;
|
||||
*im.add(b) = c.re * bi + c.im * br;
|
||||
} else if swap {
|
||||
core::ptr::swap(re.add(a), re.add(b));
|
||||
core::ptr::swap(im.add(a), im.add(b));
|
||||
} else {
|
||||
let (nr0, ni0, nr1, ni1) = pair(&m, *re.add(a), *im.add(a), *re.add(b), *im.add(b));
|
||||
*re.add(a) = nr0;
|
||||
*im.add(a) = ni0;
|
||||
*re.add(b) = nr1;
|
||||
*im.add(b) = ni1;
|
||||
}
|
||||
}
|
||||
x = ((x | fixed).wrapping_add(1)) & !fixed;
|
||||
}
|
||||
};
|
||||
let threads = threads_for(2 * groups).min(groups);
|
||||
if threads <= 1 || cfg!(target_arch = "wasm32") {
|
||||
walk(0, groups);
|
||||
return;
|
||||
}
|
||||
let share = groups.div_ceil(threads);
|
||||
std::thread::scope(|sc| {
|
||||
for t in 0..threads {
|
||||
let walk = &walk;
|
||||
sc.spawn(move || walk(t * share, ((t + 1) * share).min(groups)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Apply gates in order, fused to `width` qubits first (`width` 0 or 1
|
||||
/// applies them as given), in tiled passes ([`Schedule::DEFAULT`]).
|
||||
pub fn run(&mut self, gates: &[Gate], width: usize) -> Result<(), SvError> {
|
||||
|
|
@ -1178,6 +1305,90 @@ fn embed(g: &Gate, onto: &[u32]) -> Vec<C> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// `matrix` on `target` under `controls` as a dense gate.
|
||||
fn dense(controls: &[u32], target: u32, m: [C; 4]) -> Gate {
|
||||
let k = controls.len() + 1;
|
||||
let d = 1usize << k;
|
||||
let zero = C { re: 0.0, im: 0.0 };
|
||||
let one = C { re: 1.0, im: 0.0 };
|
||||
let mut out = vec![zero; d * d];
|
||||
for i in 0..d - 2 {
|
||||
out[i * d + i] = one;
|
||||
}
|
||||
for r in 0..2 {
|
||||
for c in 0..2 {
|
||||
out[(d - 2 + r) * d + d - 2 + c] = m[r * 2 + c];
|
||||
}
|
||||
}
|
||||
Gate { qubits: controls.iter().copied().chain([target]).collect(), matrix: out }
|
||||
}
|
||||
|
||||
/// A gate under controls is the dense gate's amplitude for amplitude, at
|
||||
/// any choice of controls and target, by every path a thread count takes;
|
||||
/// a pass under a control never touches what the control leaves off.
|
||||
#[test]
|
||||
fn a_gate_under_controls_is_its_dense_gate_on_any_thread_count() {
|
||||
use crate::gates::Gate as G;
|
||||
let n = 11;
|
||||
let start = || {
|
||||
let mut sv = StateVector::zero(n).unwrap();
|
||||
let layer: Vec<Gate> = (0..n).map(|q| G::ry(q, 0.3 + q as f64)).chain((0..n - 1).map(|q| G::cx(q, q + 1))).chain((0..n).map(|q| G::rz(q, 0.7 * q as f64))).collect();
|
||||
sv.run(&layer, 1).unwrap();
|
||||
sv
|
||||
};
|
||||
let matrices: Vec<[C; 4]> = [G::x(0), G::y(0), G::z(0), G::h(0), G::s(0), G::t(0), G::phase(0, 0.3), G::rx(0, 0.3), G::ry(0, 0.3), G::rz(0, 0.3), G::sqrt_x(0)].iter().map(|g| [g.matrix[0], g.matrix[1], g.matrix[2], g.matrix[3]]).collect();
|
||||
for (cs, t) in [(vec![0, 1], 2), (vec![10, 3], 0), (vec![1, 5, 9], 10), (vec![0, 2, 4, 6, 8], 1), (vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 0), (vec![0], 10)] {
|
||||
for &m in &matrices {
|
||||
let g = Controlled { controls: cs.clone(), target: t, matrix: m };
|
||||
let mut want = start();
|
||||
want.apply(&dense(&cs, t, m)).unwrap();
|
||||
for threads in [1, 2, 3, 8, 32] {
|
||||
let got = with_forced_threads(Some(threads), || {
|
||||
let mut sv = start();
|
||||
sv.apply_controlled(&g).unwrap();
|
||||
sv
|
||||
});
|
||||
let close = |a: &[f64], b: &[f64]| a.iter().zip(b).all(|(x, y)| (x - y).abs() <= 4.0 * f64::EPSILON);
|
||||
assert!(close(&got.re, &want.re) && close(&got.im, &want.im), "{cs:?} on {t}, {threads} threads");
|
||||
// Each thread count gives one set of bits.
|
||||
let once = with_forced_threads(Some(1), || {
|
||||
let mut sv = start();
|
||||
sv.apply_controlled(&g).unwrap();
|
||||
sv
|
||||
});
|
||||
assert_eq!(got, once, "{cs:?} on {t}, {threads} threads");
|
||||
}
|
||||
let mask = cs.iter().fold(0usize, |a, &q| a | 1 << (n - 1 - q));
|
||||
let (mut got, before) = (start(), start());
|
||||
got.apply_controlled(&g).unwrap();
|
||||
for x in 0..1usize << n {
|
||||
if x & mask != mask {
|
||||
assert_eq!((got.re[x].to_bits(), got.im[x].to_bits()), (before.re[x].to_bits(), before.im[x].to_bits()), "{x:b} under {cs:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A gate under controls is checked as a gate is.
|
||||
#[test]
|
||||
fn a_gate_under_controls_must_fit_the_register() {
|
||||
let m = [C { re: 0.0, im: 0.0 }, C { re: 1.0, im: 0.0 }, C { re: 1.0, im: 0.0 }, C { re: 0.0, im: 0.0 }];
|
||||
let mut sv = StateVector::zero(3).unwrap();
|
||||
for (controls, target) in [(vec![0, 0], 1), (vec![0, 1], 1), (vec![0, 3], 1), (vec![0, 1], 3)] {
|
||||
assert_eq!(sv.apply_controlled(&Controlled { controls, target, matrix: m }), Err(SvError::BadGate(0)));
|
||||
}
|
||||
let ops = [SvOp::Gate(crate::gates::Gate::h(0)), SvOp::Controlled(Controlled { controls: vec![0, 0], target: 1, matrix: m })];
|
||||
assert_eq!(sv.run_ops(&ops, 3), Err(SvError::BadGate(1)));
|
||||
// Refused before any gate ran.
|
||||
assert_eq!(sv, StateVector::zero(3).unwrap());
|
||||
// X under every other qubit: one exchange.
|
||||
sv.re[0b011] = 1.0;
|
||||
sv.re[0] = 0.0;
|
||||
sv.apply_controlled(&Controlled { controls: vec![1, 2], target: 0, matrix: m }).unwrap();
|
||||
assert_eq!(sv.re[0b111], 1.0);
|
||||
}
|
||||
|
||||
fn rng(seed: u64) -> impl FnMut() -> f64 {
|
||||
let mut s = seed;
|
||||
move || {
|
||||
|
|
|
|||
79
wai/wai-quantum/tests/data/color_d3.circuit
Normal file
79
wai/wai-quantum/tests/data/color_d3.circuit
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
QUBIT_COORDS(0, 0) 0
|
||||
QUBIT_COORDS(1, 0) 1
|
||||
QUBIT_COORDS(2, 0) 2
|
||||
QUBIT_COORDS(3, 0) 3
|
||||
QUBIT_COORDS(0.5, 1) 4
|
||||
QUBIT_COORDS(1.5, 1) 5
|
||||
QUBIT_COORDS(2.5, 1) 6
|
||||
QUBIT_COORDS(1, 2) 7
|
||||
QUBIT_COORDS(2, 2) 8
|
||||
QUBIT_COORDS(1.5, 3) 9
|
||||
R 0 1 2 3 4 5 6 7 8 9
|
||||
X_ERROR(0.002) 0 1 2 3 4 5 6 7 8 9
|
||||
REPEAT 2 {
|
||||
TICK
|
||||
DEPOLARIZE1(0.002) 0 1 3 5 6 7 9
|
||||
C_XYZ 0 1 3 5 6 7 9
|
||||
DEPOLARIZE1(0.002) 0 1 3 5 6 7 9
|
||||
TICK
|
||||
CX 5 4 3 2
|
||||
DEPOLARIZE2(0.002) 5 4 3 2
|
||||
TICK
|
||||
CX 7 4 6 2
|
||||
DEPOLARIZE2(0.002) 7 4 6 2
|
||||
TICK
|
||||
CX 1 4 6 8
|
||||
DEPOLARIZE2(0.002) 1 4 6 8
|
||||
TICK
|
||||
CX 1 2 7 8
|
||||
DEPOLARIZE2(0.002) 1 2 7 8
|
||||
TICK
|
||||
CX 5 2 9 8
|
||||
DEPOLARIZE2(0.002) 5 2 9 8
|
||||
TICK
|
||||
CX 0 4 5 8
|
||||
DEPOLARIZE2(0.002) 0 4 5 8
|
||||
TICK
|
||||
X_ERROR(0.002) 2 4 8
|
||||
MR 2 4 8
|
||||
X_ERROR(0.002) 2 4 8
|
||||
}
|
||||
DETECTOR(2, 0, 0) rec[-3] rec[-6]
|
||||
DETECTOR(0.5, 1, 0) rec[-2] rec[-5]
|
||||
DETECTOR(2, 2, 0) rec[-1] rec[-4]
|
||||
TICK
|
||||
DEPOLARIZE1(0.002) 0 1 3 5 6 7 9
|
||||
C_XYZ 0 1 3 5 6 7 9
|
||||
DEPOLARIZE1(0.002) 0 1 3 5 6 7 9
|
||||
TICK
|
||||
CX 5 4 3 2
|
||||
DEPOLARIZE2(0.002) 5 4 3 2
|
||||
TICK
|
||||
CX 7 4 6 2
|
||||
DEPOLARIZE2(0.002) 7 4 6 2
|
||||
TICK
|
||||
CX 1 4 6 8
|
||||
DEPOLARIZE2(0.002) 1 4 6 8
|
||||
TICK
|
||||
CX 1 2 7 8
|
||||
DEPOLARIZE2(0.002) 1 2 7 8
|
||||
TICK
|
||||
CX 5 2 9 8
|
||||
DEPOLARIZE2(0.002) 5 2 9 8
|
||||
TICK
|
||||
CX 0 4 5 8
|
||||
DEPOLARIZE2(0.002) 0 4 5 8
|
||||
TICK
|
||||
X_ERROR(0.002) 2 4 8
|
||||
MR 2 4 8
|
||||
X_ERROR(0.002) 2 4 8
|
||||
SHIFT_COORDS(0, 0, 1)
|
||||
DETECTOR(2, 0, 0) rec[-3] rec[-6] rec[-9]
|
||||
DETECTOR(0.5, 1, 0) rec[-2] rec[-5] rec[-8]
|
||||
DETECTOR(2, 2, 0) rec[-1] rec[-4] rec[-7]
|
||||
X_ERROR(0.002) 0 1 3 5 6 7 9
|
||||
M 0 1 3 5 6 7 9
|
||||
DETECTOR(2, 0, 1) rec[-3] rec[-4] rec[-5] rec[-6] rec[-10]
|
||||
DETECTOR(0.5, 1, 1) rec[-2] rec[-4] rec[-6] rec[-7] rec[-9]
|
||||
DETECTOR(2, 2, 1) rec[-1] rec[-2] rec[-3] rec[-4] rec[-8]
|
||||
OBSERVABLE_INCLUDE(0) rec[-5] rec[-6] rec[-7]
|
||||
72
wai/wai-quantum/tests/data/color_d3.dem
Normal file
72
wai/wai-quantum/tests/data/color_d3.dem
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
error(0.0006669335944047277) D0
|
||||
error(0.0074219502638438305) D0 L0
|
||||
error(0.00040026697524683395) D0 D1
|
||||
error(0.006500628512183747) D0 D1 L0
|
||||
error(0.00794764737171119) D0 D1 D2
|
||||
error(0.00013345794395852595) D0 D1 D2 D3 D4 L0
|
||||
error(0.001865528433372655) D0 D1 D2 D3 D4 D5
|
||||
error(0.00013345794395852595) D0 D1 D2 D4
|
||||
error(0.00040026697524683395) D0 D1 D2 D5
|
||||
error(0.00013345794395852595) D0 D1 D3 L0
|
||||
error(0.0021314129537955175) D0 D1 D3 D4
|
||||
error(0.0006669335944047277) D0 D1 D4
|
||||
error(0.00013345794395852595) D0 D1 D4 D5
|
||||
error(0.000533618081590258) D0 D1 D5
|
||||
error(0.006764038999123863) D0 D2
|
||||
error(0.000533618081590258) D0 D2 D3 L0
|
||||
error(0.0022643019897729626) D0 D2 D3 D5
|
||||
error(0.0017321774694031807) D0 D2 D4
|
||||
error(0.00013345794395852595) D0 D2 D4 D5
|
||||
error(0.00013345794395852595) D0 D2 D5 L0
|
||||
error(0.01200761440923498) D0 D3
|
||||
error(0.00013345794395852595) D0 D3 D4 L0
|
||||
error(0.00040026697524683395) D0 D3 D5 L0
|
||||
error(0.00013345794395852595) D0 D4
|
||||
error(0.0008002135231907214) D0 D4 L0
|
||||
error(0.000533618081590258) D0 D4 D5
|
||||
error(0.000533618081590258) D0 D5
|
||||
error(0.0074219502638438305) D1 L0
|
||||
error(0.006500628512183747) D1 D2
|
||||
error(0.000533618081590258) D1 D2 D3
|
||||
error(0.00013345794395852595) D1 D2 D3 D4 D5
|
||||
error(0.00013345794395852595) D1 D2 D4 L0
|
||||
error(0.0015995019184957544) D1 D2 D4 D5
|
||||
error(0.00026688026587144063) D1 D3 L0
|
||||
error(0.00026688026587144063) D1 D3 D4
|
||||
error(0.00040026697524683395) D1 D3 D5
|
||||
error(0.01239826888148279) D1 D4
|
||||
error(0.00026688026587144063) D1 D5
|
||||
error(0.006500628512183747) D2
|
||||
error(0.000533618081590258) D2 L0
|
||||
error(0.0014660797390328236) D2 D3
|
||||
error(0.001865173067674431) D2 D3 D4
|
||||
error(0.000533618081590258) D2 D3 D4 D5
|
||||
error(0.00013345794395852595) D2 D3 D5
|
||||
error(0.00013345794395852595) D2 D3 D5 L0
|
||||
error(0.0008002135231907214) D2 D4
|
||||
error(0.012268085484454384) D2 D5
|
||||
error(0.00026688026587144063) D3
|
||||
error(0.005971533505734127) D3 L0
|
||||
error(0.000533618081590258) D3 D4
|
||||
error(0.005047498883476306) D3 D4 L0
|
||||
error(0.00649877855081742) D3 D4 D5
|
||||
error(0.005707699784734814) D3 D5
|
||||
error(0.001066666666666516) D3 D5 D7
|
||||
error(0.006106043412479705) D3 D6
|
||||
error(0.001066666666666516) D3 D7 L0
|
||||
error(0.005971533505734127) D4 L0
|
||||
error(0.005047498883476306) D4 D5
|
||||
error(0.006106043412479705) D4 D7
|
||||
error(0.005047498883476306) D5
|
||||
error(0.00040026697524683395) D5 L0
|
||||
error(0.001066666666666516) D5 D6
|
||||
error(0.001066666666666516) D5 D6 D7
|
||||
error(0.001066666666666516) D5 D7
|
||||
error(0.006106043412479705) D5 D8
|
||||
error(0.0025314836092638973) D6 L0
|
||||
error(0.0025314836092638973) D6 D7 L0
|
||||
error(0.0025314836092638973) D6 D7 D8
|
||||
error(0.0025314836092638973) D6 D8
|
||||
error(0.0025314836092638973) D7 L0
|
||||
error(0.0025314836092638973) D7 D8
|
||||
error(0.0025314836092638973) D8
|
||||
|
|
@ -36,11 +36,12 @@ fn generated() -> Vec<(&'static str, &'static str, String)> {
|
|||
}
|
||||
|
||||
/// The hand-written circuits and models, kept as committed.
|
||||
const WRITTEN_CIRCUITS: [&str; 1] = ["circuits/tagged.circuit"];
|
||||
const WRITTEN_CIRCUITS: [&str; 2] = ["circuits/tagged.circuit", "circuits/correlated.circuit"];
|
||||
const WRITTEN_MODELS: [&str; 1] = ["models/decomposed.dem"];
|
||||
|
||||
/// What each refused file must be refused for: (file, line, words).
|
||||
const REFUSED: [(&str, usize, &str); 7] = [
|
||||
const REFUSED: [(&str, usize, &str); 8] = [
|
||||
("refuse/no_independent_flips.circuit", 2, "no form as independent"),
|
||||
("refuse/unsupported.circuit", 3, "unsupported instruction 'T'"),
|
||||
("refuse/random_observable.circuit", 0, "not deterministic"),
|
||||
("refuse/record.circuit", 3, "measurement record out of range"),
|
||||
|
|
|
|||
Loading…
Reference in a new issue