diff --git a/.github/workflows/wai-quantum-service.yml b/.github/workflows/wai-quantum-service.yml index 8cabfeaf3..541116696 100644 --- a/.github/workflows/wai-quantum-service.yml +++ b/.github/workflows/wai-quantum-service.yml @@ -101,6 +101,17 @@ jobs: set -euo pipefail nice -n 10 cargo test --locked --release 2>&1 | tee "$RUNNER_TEMP/test.log" grep -qE '^test result: ok\. [1-9][0-9]* passed; 0 failed' "$RUNNER_TEMP/test.log" || { echo "::error::no test ran"; exit 1; } + # The single-precision state vector on this host's GPU, against the + # double-precision one; the GPU is declared, so its absence fails. + - name: The GPU state vector + shell: bash + working-directory: wai/wai-quantum + env: + WAI_QUANTUM_SV_METAL_EXPECT: "1" + run: | + set -euo pipefail + nice -n 10 cargo test --locked --release --features quantum_sv_metal --lib quantum_sv_metal 2>&1 | tee "$RUNNER_TEMP/gpu.log" + grep -qE '^test result: ok\. 4 passed; 0 failed' "$RUNNER_TEMP/gpu.log" || { echo "::error::not every GPU test ran and passed"; exit 1; } - name: The smoke checks over HTTP shell: bash working-directory: wai/wai-quantum-service diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index eea93f6ef..9c9fdaf88 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,116 @@ wire format may change and such changes are recorded here. ## v0.4.0 — current +### wai-quantum 0.6.4: noisy Clifford circuits by Pauli frames, the state vector on the GPU, and the density matrix by superoperators + +Additive. Every pinned digest is unchanged. + +- **`DensityMatrix::evolve`** (`quantum_noisy`): the exact noisy state on up to 16 qubits + (`MAX_EVOLVE_QUBITS`), against 12 for `DensityMatrix::run`, which stays as the referee. + - Each gate goes on `ρ`'s rows, its conjugate on the columns. Each channel becomes one + superoperator `Σᵢ wᵢ Kᵢ ⊗ Kᵢ*` on its qubits' row and column bits, in `run`'s order. + - A gate and its noise fuse into one gate on at most four of the vector's qubits, applied + in the state vector's tiled passes. A gate therefore costs one pass and no copy of `ρ`. + - **Checked:** against `run`, every entry within 10⁻¹² on circuits with every channel and + a Toffoli. At 14 qubits, past `run`'s size, against branching trajectories within four + standard errors. + - **Measured** on a 16-core desktop processor: 12 qubits in 0.53 s against 158 s for + `run`, and 10 qubits in 39 ms against 4.1 s. 15 qubits with 132 noisy gates take 49 s. + - **The service's `density_matrix`** runs on it, up to 16 qubits within a job's memory and + a work cap. `auto`, with noise, still tries it first. +- **`quantum_spd::expectation_capped`**: the sparse-Pauli engine held to a number of terms. + Whenever a rotation leaves more, the threshold doubles and the sum is truncated again + until it fits, and everything dropped that way joins the bound. A run that never reaches + the cap is the uncapped run, bit for bit. + - **Checked:** on the 127-qubit kicked-Ising circuit, a cap above the peak changes no bit. + A cap a tenth of the peak stays within its bound of the uncapped value. On a small + register, the bound covers the exact error. + - **The service's `pauli`** holds as many terms as the job's memory allows (`max_terms` + can lower it). A sum that would have outgrown the job now finishes at a coarser + threshold, reported as `threshold_reached`, with its bound. +- **The tensor-network executor blocked and threaded** (`quantum_tn`). Each pairwise + contraction is a complex matrix product blocked for the cache, its rows shared between + threads. Slices running in parallel keep their contractions on one thread each. Every + output adds its terms in the same order, so the result is the same bits (a test holds it + to the plain loop's, and the pinned digests hold). + - **Measured:** large products went from 2.0 to about 25 billion complex multiply-adds a + second on a 16-core desktop processor (1024 × 1024 × 1024: 0.52 s → 0.043 s). +- **Faster state-vector kernels** (`quantum_sv`). Gates on two to five qubits run at + compile-time sizes, several groups side by side, with groups of low strides gathered. + Every output is the same sum in the same order, so the state is the same bits. + - Measured in cache on one core of a desktop machine: two-qubit gates 280–345 → 400–460 + million amplitudes a second, and dense three- and four-qubit gates 10–25% faster. + - On a 16-core desktop processor, dense three- to five-qubit gates are 35–50% faster. + +- **`quantum_sv_metal`** (new feature, macOS): the dense state vector in single precision + on the platform's GPU, in one buffer the GPU and the host share. Half the bytes of the + double-precision state, so one more qubit fits. + - Gates are fused as `quantum_sv` fuses them, then grouped into tile passes. Each + threadgroup copies 4096 amplitudes into threadgroup memory, applies every gate of the + pass there between barriers, and copies them back. + - A kernel is built per group size, so each group stays in registers. The kernels are + compiled with the platform's safe math mode. Norms, expectations and samples are summed + on the host in double precision. + - **Checked:** + - Against the double-precision state vector on random circuits of 6 to 21 qubits at + every fusion width: amplitudes within 2·10⁻⁶, expectations within 10⁻⁵. + - Tiled and untiled runs are the same bits, and repeated runs are the same bits. + - **Measured** on a desktop machine's GPU, with the machine heavily loaded by other work: + a 30-qubit, depth-20 brickwork of 910 gates runs in about 4.2 s at fusion width 3 with + tiles, against about 8 s gate by gate. + - **The service** offers it as `statevector_single`, accuracy class `single`, where a GPU + is present. `auto` tries it after the double-precision `statevector`, so it takes the + circuits that one cannot hold: up to 33 qubits within a job's memory. + - **Measured** through the public service, on a host under heavy load: + - a 33-qubit job (a 64 GiB state, 97 gates, chosen by `auto`) in 70 s; + - 31 qubits in 23.5 s; + - 26 qubits in 2.1 s, against 10.1 s for the double-precision method. The expectation + values agree to 10⁻⁸. + +- **`quantum_tableau::frame_counts` and `frame_expectations`.** Clifford circuits under Pauli + noise (`FrameStep`: depolarizing on one or two qubits, dephasing; readout flips), on up + to 32768 qubits. + - One noiseless outcome from the tableau is the reference. Each shot carries a Pauli + frame, the difference its noise makes, 64 shots to a machine word. + - Frames start with random Z bits. They leave `|0…0⟩` unchanged, and carried through the + circuit they spread each shot uniformly over the noiseless outcomes. + - Errors are placed by geometric gaps, with the crate's own logarithm. + - An expectation value is the noiseless value times the frames' mean sign against the + string, with its standard error. + - Both readouts are the same bits at any thread count. +- **Checked:** on random Clifford circuits of 3 to 5 qubits with every channel, the counts + (200,000 shots, with readout flips) and the expectation values agree with the exact + density matrix within five standard errors. Two mutations are caught: dropping one qubit + of the two-qubit channel, and dropping the random Z bits. +- **Measured** on a 16-core desktop processor: 100,000 full-width shots take 44 ms at 1,000 + qubits with 10,000 noisy gates, and 1.6 s at 10,000 qubits with 100,000. +- **The service's `stabilizer` method takes Pauli noise.** Each QASM gate gets its noise on + the qubits it touched, as the other noisy methods apply it. Expectations are estimated + from at least 10,000 shots unless `trajectories` says otherwise. Amplitude damping is not + a Pauli channel and is refused. With noise, `auto` tries `density_matrix` (12 qubits, + exact), then `stabilizer`, then `trajectories`. +- **The OpenQASM writer keeps every gate** (`quantum_toolchain::to_qasm`). A gate with + three or more controls, such as Grover's oracle and diffusion past three qubits, was + written as a comment, so the program written out was not the circuit. Every controlled + gate without a standard name is now written in OpenQASM 3's modifier form, + `ctrl(n) @ g`, and `from_qasm` reads `ctrl`, `ctrl(n)`, `negctrl` and `negctrl(n)` back. + - **Checked:** each base gate under zero to five controls is read back op for op, and + Grover's search on five and seven qubits keeps its state. +- **The service** (`wai-quantum-service`, not published): + - It reads the control modifiers on `x y z h s sdg t tdg p` and on `cx cy cz ch cp ccx + ccz`. A gate with a standard name is read as that gate, so a Clifford program keeps + the stabilizer method. A phase on m qubits is written exactly with 2^m − 1 `cx` and + as many `p`, its angles dyadic when the phase is. **Checked:** on a 6-qubit state, + every amplitude against the gate applied directly where the controls hold, within + 10⁻¹², each test failing when the expansion's signs or its releases are broken. + - Receipts sign the RFC 8785 canonical text of the request and the result, so a result + verifies after any JSON reader has carried it, a browser's included. Its numbers are + checked against a JavaScript engine on RFC 8785's appendix B and on 2 million random + doubles. Receipts made before it still verify. + - It reads numbers correctly rounded (serde_json's `float_roundtrip`). Before, a result + could change in its last bit on its way from the job's process to the reply. + - A job carries its request as submitted, so whoever holds its id can check its receipt. + ### `wai.image.jpegai`'s hyper decoders, context stages and synthesis through the integer operator set - **§13.6.2 is normative.** [integer-operators](extensions/integer-operators.md) §13.6.2 diff --git a/wai/tools/quantum-meter/Cargo.lock b/wai/tools/quantum-meter/Cargo.lock index 002097d96..aed470f60 100644 --- a/wai/tools/quantum-meter/Cargo.lock +++ b/wai/tools/quantum-meter/Cargo.lock @@ -1017,7 +1017,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wai-quantum" -version = "0.6.3" +version = "0.6.4" dependencies = [ "blake3", "ed25519-dalek", diff --git a/wai/tools/quantum-wgsl-check/Cargo.lock b/wai/tools/quantum-wgsl-check/Cargo.lock index 5d234c140..0398234a3 100644 --- a/wai/tools/quantum-wgsl-check/Cargo.lock +++ b/wai/tools/quantum-wgsl-check/Cargo.lock @@ -599,7 +599,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wai-quantum" -version = "0.6.3" +version = "0.6.4" dependencies = [ "blake3", "ed25519-dalek", diff --git a/wai/wai-pack/Cargo.lock b/wai/wai-pack/Cargo.lock index 0e815b544..686cab658 100644 --- a/wai/wai-pack/Cargo.lock +++ b/wai/wai-pack/Cargo.lock @@ -713,7 +713,7 @@ dependencies = [ [[package]] name = "wai-quantum" -version = "0.6.3" +version = "0.6.4" dependencies = [ "blake3", "ed25519-dalek", diff --git a/wai/wai-quantum-qpu/Cargo.lock b/wai/wai-quantum-qpu/Cargo.lock index 2b01b5768..f7017f72d 100644 --- a/wai/wai-quantum-qpu/Cargo.lock +++ b/wai/wai-quantum-qpu/Cargo.lock @@ -422,7 +422,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wai-quantum" -version = "0.6.3" +version = "0.6.4" dependencies = [ "blake3", "ed25519-dalek", diff --git a/wai/wai-quantum-serve/Cargo.lock b/wai/wai-quantum-serve/Cargo.lock index a4e96f869..dbefe1eff 100644 --- a/wai/wai-quantum-serve/Cargo.lock +++ b/wai/wai-quantum-serve/Cargo.lock @@ -599,7 +599,7 @@ dependencies = [ [[package]] name = "wai-quantum" -version = "0.6.3" +version = "0.6.4" dependencies = [ "blake3", "ed25519-dalek", diff --git a/wai/wai-quantum-service/Cargo.lock b/wai/wai-quantum-service/Cargo.lock index 8dc8c3c70..98a0ba144 100644 --- a/wai/wai-quantum-service/Cargo.lock +++ b/wai/wai-quantum-service/Cargo.lock @@ -72,6 +72,12 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + [[package]] name = "blake3" version = "1.8.7" @@ -468,6 +474,42 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "objc2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags", + "objc2", + "objc2-foundation", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -815,10 +857,13 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wai-quantum" -version = "0.6.3" +version = "0.6.4" dependencies = [ "blake3", "ed25519-dalek", + "objc2", + "objc2-foundation", + "objc2-metal", "serde_json", ] diff --git a/wai/wai-quantum-service/Cargo.toml b/wai/wai-quantum-service/Cargo.toml index 0ffddfb82..ee8fb90de 100644 --- a/wai/wai-quantum-service/Cargo.toml +++ b/wai/wai-quantum-service/Cargo.toml @@ -12,10 +12,12 @@ publish = false axum = "0.8" tokio = { version = "1", features = ["rt-multi-thread", "macros", "process", "time", "sync", "net", "io-util"] } serde = { version = "1", features = ["derive"] } -serde_json = "1" +# float_roundtrip: a number a client sends back reads as the double it was +# (the default reader rounds twice), so its receipt still verifies. +serde_json = { version = "1", features = ["float_roundtrip"] } blake3 = "1" getrandom = "0.4" -wai-quantum = { path = "../wai-quantum", default-features = false, features = ["quantum", "quantum_toolchain", "quantum_receipt", "quantum_sv", "quantum_tn", "quantum_tableau", "quantum_mpsim", "quantum_spd", "quantum_noisy"] } +wai-quantum = { path = "../wai-quantum", default-features = false, features = ["quantum", "quantum_toolchain", "quantum_receipt", "quantum_sv", "quantum_tn", "quantum_tableau", "quantum_mpsim", "quantum_spd", "quantum_noisy", "quantum_sv_metal"] } [profile.release] lto = "thin" diff --git a/wai/wai-quantum-service/README.md b/wai/wai-quantum-service/README.md index 22b81717d..85fd98ff2 100644 --- a/wai/wai-quantum-service/README.md +++ b/wai/wai-quantum-service/README.md @@ -28,31 +28,36 @@ A job is JSON: Counts are keyed by the program's classical bits, the highest bit first. With no measurement in the program, every qubit is measured into the bit of its own index. +The program may use the standard gates, user `gate` definitions, and the control modifiers `ctrl @`, `ctrl(n) @`, `negctrl @` and `negctrl(n) @` (on `x y z h s sdg t tdg p` and `cx cy cz ch cp ccx ccz`). Classical control, `inv @`, `pow @` and gates after a qubit's measurement are refused, each with its line and what to write instead (`src/qasm.rs` lists everything read). + ## Methods | method | takes | answers | accuracy | |---|---|---|---| | `exact` | angles that are dyadic multiples of 2π, up to 26 qubits | counts, expectations, amplitudes, a state hash | bit-exact | | `statevector` | any circuit, up to 32 qubits | counts, expectations, amplitudes | exact | -| `stabilizer` | Clifford circuits (rotations by whole quarter turns), up to 32768 qubits | counts, expectations | exact | +| `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 | | `tensor_network` | any circuit | amplitudes | exact | -| `density_matrix` | noisy circuits, up to 12 qubits | counts, expectations | exact | +| `density_matrix` | noisy circuits, up to 16 qubits within a job's memory | counts, expectations | exact | | `trajectories` | noisy circuits, up to 32 qubits | counts, expectations | estimate | How the methods run: - **statevector** groups consecutive gates into passes over the state. Each pass applies its gates tile by tile, in a buffer that fits a core's cache. The grouping never changes a bit of the result. -- **pauli** reports each value with its truncation `bound`. Unless `ladder` is `false`, it also reports a `ladder`: the value and bound at up to three thresholds a decade above the one asked for, so you can see the value settle as the threshold drops. +- **pauli** reports each value with its truncation `bound`. It holds at most as many Pauli terms as the job's memory allows; a sum that would outgrow it raises its threshold until it fits (`threshold_reached`), and the bound covers everything that dropped. Unless `ladder` is `false`, it also reports a `ladder`: the value and bound at up to three thresholds a decade above the one asked for, so you can see the value settle as the threshold drops. +- **stabilizer**, with noise, samples by Pauli frames against one noiseless outcome of the tableau, 64 shots to a machine word. Its counts have the law of independent noisy shots. Its expectation values are estimates with standard errors, from at least 10,000 shots unless `trajectories` sets the number. Amplitude damping is not a Pauli channel and is refused. - **trajectories** treats every shot as an independent noisy trajectory in law. Shots that took the same errors share one state, so the error-free part of a circuit, and the error-free majority of the shots, run once. `histories` in the result counts the distinct error histories that ran. -`auto` picks the first method that takes the job. With noise, it tries `density_matrix` then `trajectories`. For counts, it tries `stabilizer`, `exact`, `statevector`, then `mps`. For expectations alone, it tries `stabilizer`, `exact`, `statevector`, `pauli`, then `mps`. For amplitudes alone, it tries `exact`, `statevector`, `tensor_network`, then `mps`. The job's `why` names the method chosen and the ones that could not take it. +`auto` picks the first method that takes the job. With noise, it tries `density_matrix`, then `stabilizer`, then `trajectories`. For counts, it tries `stabilizer`, `exact`, `statevector`, `statevector_single`, then `mps`. For expectations alone, it tries `stabilizer`, `exact`, `statevector`, `statevector_single`, `pauli`, then `mps`. For amplitudes alone, it tries `exact`, `statevector`, `statevector_single`, `tensor_network`, then `mps`. The job's `why` names the method chosen and the ones that could not take it. The accuracy classes: - **bit-exact**: the same bits on every machine. Amplitudes are held in 30-bit fixed point, and every output is an exact function of them. - **exact**: no approximation beyond double-precision rounding. +- **single**: no approximation beyond single-precision rounding, about 10⁻⁷ relative per gate; sums are taken in double precision. - **bounded**: an approximation, with a rigorous bound on the error (`bound`) beside each value. - **estimate**: a statistical estimate, with its standard error (`stderr`). - **heuristic**: an approximation, reported with the standard estimate of its error but not a bound. A matrix product state that truncated reports its discarded weight and its fidelity estimate, and a `warning` when the estimate is below 0.9. @@ -66,7 +71,7 @@ Counts are always seeded samples. | `GET /v1` | the service: its methods, limits, load and receipt key | | `POST /v1/run` | submit a job and wait up to 25 s: the finished job, or `202` and the job if it is still running | | `POST /v1/jobs` | submit a job: `202` and the job, with its `id` | -| `GET /v1/jobs/{id}` | the job: its status, and its result when it is done | +| `GET /v1/jobs/{id}` | the job: its status, the request as submitted, and its result when it is done | | `DELETE /v1/jobs/{id}` | cancel a queued or running job | | `POST /v1/verify` | `{"request": ..., "result": ...}`: whether the result's receipt seals that request and that result | @@ -74,7 +79,12 @@ Every refusal says why: `400` for a program or job that does not read, `422` for ## Receipts -Every finished result carries a `receipt`: an Ed25519 signature, by the key `GET /v1` publishes, over the BLAKE3 digest of the request as submitted and the result as returned. `POST /v1/verify` checks one, and so can any Ed25519 implementation (see `src/receipt.rs`). +Every finished result carries a `receipt`: an Ed25519 signature, by the key `GET /v1` publishes, over the BLAKE3 digest of the request as submitted and the result as returned. `POST /v1/verify` checks one, and so can any Ed25519 implementation: + +- digest = BLAKE3(`wai:quantum-service-receipt` ‖ 0x02 ‖ len(R) ‖ R ‖ len(S) ‖ S), each len a big-endian u64; +- R and S are the RFC 8785 canonical texts (`"canon": "rfc8785"`) of the request and of the result without its `receipt`: keys sorted, no whitespace, every number written as ECMAScript writes the double it reads. + +So a result verifies after any JSON reader has carried it, a browser's included. Whole numbers in a request must be within ±(2^53 − 1), so that every reader reads them alike. Receipts issued before the canonical form carry no `canon` and still verify (see `src/receipt.rs`). ## Running it diff --git a/wai/wai-quantum-service/smoke.sh b/wai/wai-quantum-service/smoke.sh index db3734590..757084cf1 100644 --- a/wai/wai-quantum-service/smoke.sh +++ b/wai/wai-quantum-service/smoke.sh @@ -49,6 +49,24 @@ json.dump({"request": req, "result": res}, open(sys.argv[4],"w"))' "$tmp/ghz.jso post /v1/verify "$tmp/v.json" | py 'import json,sys; d=json.load(sys.stdin); assert d["valid"] is True, d; print("receipt: verifies")' post /v1/verify "$tmp/t.json" | py 'import json,sys; d=json.load(sys.stdin); assert d["valid"] is False, d; print("receipt: a changed result does not verify")' +# The job carries its request as submitted, so whoever holds only the id can +# check the receipt: verify from the service's answer alone. +py 'import json,sys +req=json.load(open(sys.argv[1])); job=json.load(open(sys.argv[2])) +assert job["request"]==req, job.get("request") +json.dump({"request": job["request"], "result": job["result"]}, open(sys.argv[3],"w"))' "$tmp/ghz.json" "$tmp/ghz.out" "$tmp/vj.json" +post /v1/verify "$tmp/vj.json" | py 'import json,sys; d=json.load(sys.stdin); assert d["valid"] is True, d; print("receipt: verifies from the job alone")' + +# As a browser sends it back: whole doubles without a fraction, keys reversed. +py 'import json,sys +def js(v): + if isinstance(v, float) and v.is_integer(): return int(v) + if isinstance(v, dict): return {k: js(v[k]) for k in reversed(list(v))} + if isinstance(v, list): return [js(x) for x in v] + return v +v=json.load(open(sys.argv[1])); json.dump(js(v), open(sys.argv[2],"w"))' "$tmp/vj.json" "$tmp/vjs.json" +post /v1/verify "$tmp/vjs.json" | py 'import json,sys; d=json.load(sys.stdin); assert d["valid"] is True, d; print("receipt: verifies after a browser-style round trip")' + # Continuous angles on a small circuit: the dense state vector, and the # probabilities sum to the shots. py 'import json; print(json.dumps({"qasm": "qubit[3] q; rx(0.3) q[0]; ry(1.2) q[1]; cx q[0], q[2]; crz(0.7) q[1], q[2];", "shots": 500, "observables": ["Z0", "Z1 Z2"]}))' > "$tmp/rot.json" @@ -96,6 +114,91 @@ assert sum(r["counts"].values())==2000, r h=r["accuracy"]["histories"]; assert 1 <= h < 2000, r["accuracy"] print("noisy-14: ok,", r["method"], h, "histories for 2000 shots")' "$tmp/noisy.out" +# A wide noisy Clifford circuit: Pauli frames on the stabilizer tableau. +py 'import json +n=500 +q="qubit[%d] q;\n" % n + "h q[0];\n" + "".join("cx q[%d], q[%d];\n" % (i,i+1) for i in range(n-1)) +print(json.dumps({"qasm": q, "shots": 1000, "noise": {"depolarizing_2q": 0.0005, "readout": 0.001}, "observables": ["Z0 Z499"]}))' > "$tmp/nghz.json" +post /v1/run "$tmp/nghz.json" > "$tmp/nghz.out" +py 'import json,sys +d=json.load(open(sys.argv[1])); assert d.get("status")=="done", d; r=d["result"] +assert r["method"]=="stabilizer" and r["accuracy"]["class"]=="estimate", d +assert sum(r["counts"].values())==1000, r +e=r["expectations"][0]; assert 0.5 < e["value"] < 1.0 and e["stderr"] > 0, e +print("noisy-ghz-500: ok,", r["method"], " =", round(e["value"],4), "±", round(e["stderr"],4))' "$tmp/nghz.out" + +# The single-precision state vector on the GPU, where the service has one, +# against the double-precision one. +if py 'import json,sys; d=json.load(open(sys.argv[1])); sys.exit(0 if any(m["method"]=="statevector_single" and m.get("available") for m in d["methods"]) else 1)' "$tmp/describe.json"; then + py 'import json; print(json.dumps({"qasm": "qubit[12] q; h q; rx(0.3) q[0]; cx q[0], q[11]; crz(0.7) q[3], q[9]; ry(1.1) q[5];", "observables": ["Z0 Z11", "X5"], "method": "statevector_single"}))' > "$tmp/gpu.json" + py 'import json,sys; d=json.load(open(sys.argv[1])); d["method"]="statevector"; json.dump(d, open(sys.argv[2],"w"))' "$tmp/gpu.json" "$tmp/cpu.json" + post /v1/run "$tmp/gpu.json" > "$tmp/gpu.out" + post /v1/run "$tmp/cpu.json" > "$tmp/cpu.out" + py 'import json,sys +g=json.load(open(sys.argv[1])); c=json.load(open(sys.argv[2])); assert g.get("status")=="done" and c.get("status")=="done", (g, c) +gv=[e["value"] for e in g["result"]["expectations"]]; cv=[e["value"] for e in c["result"]["expectations"]] +assert g["result"]["accuracy"]["class"]=="single" and all(abs(a-b)<1e-5 for a,b in zip(gv,cv)), (gv, cv) +print("gpu: ok,", g["result"]["accuracy"]["device"], gv)' "$tmp/gpu.out" "$tmp/cpu.out" +else + echo "gpu: this instance has none" +fi + +# What a program finds first: the index, the examples, the OpenAPI document. +curl -sS --max-time 30 -H 'accept: application/json' "$U/" > "$tmp/index.json" +py 'import json,sys; d=json.load(open(sys.argv[1])); assert d["mcp"].endswith("/mcp") and d["openapi"].endswith("/v1/openapi.json"), d; print("index: ok,", d["mcp"])' "$tmp/index.json" +curl -sS --max-time 30 "$U/v1/examples" > "$tmp/examples.json" +curl -sS --max-time 30 "$U/v1/openapi.json" > "$tmp/openapi.json" +py 'import json,sys +e=json.load(open(sys.argv[1]))["examples"]; o=json.load(open(sys.argv[2])) +assert len(e)>=5 and all("job" in x and "qasm" in x["job"] for x in e), e +assert o["openapi"].startswith("3.1") and "/v1/run" in o["paths"] and "/mcp" in o["paths"], list(o["paths"]) +print("examples and openapi: ok,", len(e), "examples,", len(o["paths"]), "paths")' "$tmp/examples.json" "$tmp/openapi.json" + +# An agent over the Model Context Protocol: initialize, list the tools, run +# the first example, and check its receipt. +mcp() { curl -sS --max-time 120 -X POST "$U/mcp" -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' --data-binary "$1"; } +mcp '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"1"}}}' > "$tmp/mcp-init.json" +code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 30 -X POST "$U/mcp" -H 'content-type: application/json' --data-binary '{"jsonrpc":"2.0","method":"notifications/initialized"}') +mcp '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' > "$tmp/mcp-tools.json" +py 'import json,sys +i=json.load(open(sys.argv[1]))["result"]; t=json.load(open(sys.argv[2]))["result"]["tools"] +assert i["protocolVersion"]=="2025-06-18" and "tools" in i["capabilities"], i +names={x["name"] for x in t}; assert {"run_quantum_circuit","get_quantum_job","verify_quantum_receipt","quantum_service_info","quantum_circuit_examples"} <= names, names +assert sys.argv[3]=="202", sys.argv[3] +print("mcp: ok, initialized,", len(t), "tools")' "$tmp/mcp-init.json" "$tmp/mcp-tools.json" "$code" +py 'import json,sys +job=json.load(open(sys.argv[1]))["examples"][0]["job"] +print(json.dumps({"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"run_quantum_circuit","arguments":job}}))' "$tmp/examples.json" > "$tmp/mcp-call.json" +mcp "@$tmp/mcp-call.json" > "$tmp/mcp-run.json" +py 'import json,sys +r=json.load(open(sys.argv[1]))["result"]; job=r["structuredContent"]; text=r["content"][0]["text"] +assert not r["isError"] and job["status"]=="done" and "counts" in job["result"], r +assert "method:" in text and "counts" in text, text +req=json.load(open(sys.argv[2]))["examples"][0]["job"] +print(json.dumps({"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"verify_quantum_receipt","arguments":{"request":req,"result":job["result"]}}}))' "$tmp/mcp-run.json" "$tmp/examples.json" > "$tmp/mcp-verify.json" +curl -sS --max-time 60 -X POST "$U/mcp" -H 'content-type: application/json' --data-binary "@$tmp/mcp-verify.json" > "$tmp/mcp-verified.json" +py 'import json,sys +v=json.load(open(sys.argv[1]))["result"]; assert v["structuredContent"]["valid"] is True, v +print("mcp run + verify: ok,", json.load(open(sys.argv[2]))["result"]["content"][0]["text"].splitlines()[1])' "$tmp/mcp-verified.json" "$tmp/mcp-run.json" + +# Grover's search on five qubits as the IDE writes it: its oracle and its +# diffusion are each a Z under four controls. Four rounds find the item. +py 'import json +n, marked = 5, 0b10110 +def mcz(): return "ctrl(4) @ z q[0],q[1],q[2],q[3],q[4];\n" +q = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[5] q;\n" + "".join("h q[%d];\n" % i for i in range(n)) +for _ in range(4): + flips = "".join("x q[%d];\n" % i for i in range(n) if not marked >> i & 1) + q += flips + mcz() + flips + q += "".join("h q[%d];\nx q[%d];\n" % (i, i) for i in range(n)) + mcz() + "".join("x q[%d];\nh q[%d];\n" % (i, i) for i in range(n)) +print(json.dumps({"qasm": q, "shots": 1000}))' > "$tmp/grover.json" +post /v1/run "$tmp/grover.json" > "$tmp/grover.out" +py 'import json,sys +d=json.load(open(sys.argv[1])); r=d["result"] +hits=r["counts"].get("10110", 0) +assert d["status"]=="done" and hits > 980, d +print("grover-5 (ctrl modifiers): ok,", r["method"], hits, "of 1000 on the marked item")' "$tmp/grover.out" + # A refusal says why. py 'import json; print(json.dumps({"qasm": "qubit[2] q; if (c) x q;"}))' > "$tmp/bad.json" code=$(curl -sS -o "$tmp/bad.out" -w '%{http_code}' -X POST "$U/v1/jobs" -H 'content-type: application/json' --data-binary "@$tmp/bad.json") diff --git a/wai/wai-quantum-service/src/catalog.rs b/wai/wai-quantum-service/src/catalog.rs index 69d481221..90b816e75 100644 --- a/wai/wai-quantum-service/src/catalog.rs +++ b/wai/wai-quantum-service/src/catalog.rs @@ -9,17 +9,19 @@ //! |---|---|---|---| //! | `exact` | dyadic angles, ≤ 26 qubits | counts, expectations, amplitudes, state hash | bit-exact | //! | `statevector` | any, up to the memory a job has (32 qubits) | counts, expectations, amplitudes | exact | -//! | `stabilizer` | Clifford, ≤ 32768 qubits | counts, expectations | exact | +//! | `statevector_single` | gates on ≤ 4 qubits, ≤ 33 qubits, on a GPU | counts, expectations, amplitudes | single | +//! | `stabilizer` | Clifford, ≤ 32768 qubits, optionally with Pauli noise | counts, expectations | exact; an estimate with noise | //! | `pauli` | any, ≤ 1024 qubits | expectations | bounded | //! | `mps` | any, ≤ 4096 qubits, best on low-entanglement circuits | counts, expectations, amplitudes | exact, or heuristic with its fidelity estimate | //! | `tensor_network` | any | amplitudes | exact | -//! | `density_matrix` | any, noisy, ≤ 12 qubits | counts, expectations | exact | +//! | `density_matrix` | any, noisy, ≤ 16 qubits in a job's memory | counts, expectations | exact | //! | `trajectories` | any, noisy, ≤ 32 qubits | counts, expectations | estimate | //! -//! `auto` picks, in order: with noise, `density_matrix` then `trajectories`; -//! for counts, `stabilizer`, `exact`, `statevector`, `mps`; for expectations -//! only, `stabilizer`, `exact`, `statevector`, `pauli`, `mps`; for amplitudes -//! only, `exact`, `statevector`, `tensor_network`, `mps`. A method that cannot take the circuit is +//! `auto` picks, in order: with noise, `density_matrix`, `stabilizer`, then `trajectories`; +//! for counts, `stabilizer`, `exact`, `statevector`, `statevector_single`, +//! `mps`; for expectations only, `stabilizer`, `exact`, `statevector`, +//! `statevector_single`, `pauli`, `mps`; for amplitudes only, `exact`, +//! `statevector`, `statevector_single`, `tensor_network`, `mps`. A method that cannot take the circuit is //! skipped, and the plan says why the one chosen was chosen. use crate::qasm::{self, Program}; @@ -35,7 +37,10 @@ pub const MAX_OUTCOMES: usize = 65_536; pub const EXACT_MAX_QUBITS: u32 = 26; pub const STABILIZER_MAX_QUBITS: u32 = wai_quantum::quantum_tableau::MAX_QUBITS; pub const PAULI_MAX_QUBITS: u32 = 1024; -pub const DENSITY_MAX_QUBITS: u32 = 12; +pub const DENSITY_MAX_QUBITS: u32 = wai_quantum::quantum_noisy::MAX_EVOLVE_QUBITS; +/// The most a density-matrix job may do: its gates times the `4ⁿ` entries +/// of `ρ`, a few minutes of passes. +pub const DENSITY_MAX_WORK: u64 = 1_000_000_000_000; pub const TRAJECTORY_MAX_QUBITS: u32 = 32; pub const MPS_MAX_QUBITS: u32 = wai_quantum::quantum_mpsim::MAX_QUBITS; /// The bond dimension an `mps` job gets unless it asks, and the most it may. @@ -80,11 +85,12 @@ pub fn describe(job_bytes: u64) -> Value { json!([ { "method": "exact", "accuracy": "bit-exact", "circuits": "every angle a dyadic multiple of 2π (2πm/2^k, k ≤ 32)", "max_qubits": EXACT_MAX_QUBITS, "answers": ["counts", "expectations", "amplitudes", "state_hash"] }, { "method": "statevector", "accuracy": "exact", "circuits": "any", "max_qubits": statevector_max_qubits(job_bytes), "answers": ["counts", "expectations", "amplitudes"] }, - { "method": "stabilizer", "accuracy": "exact", "circuits": "Clifford (rotations by whole quarter turns, controlled phases by whole half turns)", "max_qubits": STABILIZER_MAX_QUBITS, "answers": ["counts", "expectations"] }, - { "method": "pauli", "accuracy": "bounded", "circuits": "any", "max_qubits": PAULI_MAX_QUBITS, "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)" } }, + { "method": "statevector_single", "accuracy": "single", "circuits": "any, gates on at most four qubits", "max_qubits": gpu_max_qubits().map(|m| m.min(single_max_qubits(job_bytes))).unwrap_or(0), "available": gpu_max_qubits().is_ok(), "answers": ["counts", "expectations", "amplitudes"], "options": { "fusion": "the widest fused gate (default 3, at most 3)" } }, + { "method": "stabilizer", "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", "max_qubits": STABILIZER_MAX_QUBITS, "answers": ["counts", "expectations"], "options": { "trajectories": "with noise: the shots expectation values are estimated from (default: the shots, at least 10000)" } }, + { "method": "pauli", "accuracy": "bounded", "circuits": "any", "max_qubits": PAULI_MAX_QUBITS, "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" } }, { "method": "mps", "accuracy": "exact when nothing is truncated, otherwise heuristic", "circuits": "any; best on low-entanglement circuits", "max_qubits": MPS_MAX_QUBITS, "answers": ["counts", "expectations", "amplitudes"], "options": { "max_bond": "the largest bond dimension kept (default 64, at most 256)", "cutoff": "singular values whose squared share is below it are dropped (default 1e-12)" } }, { "method": "tensor_network", "accuracy": "exact", "circuits": "any", "max_qubits": qasm::MAX_QUBITS, "answers": ["amplitudes"] }, - { "method": "density_matrix", "accuracy": "exact", "circuits": "any, with noise", "max_qubits": DENSITY_MAX_QUBITS, "answers": ["counts", "expectations"] }, + { "method": "density_matrix", "accuracy": "exact", "circuits": "any, with noise", "max_qubits": density_max_qubits(job_bytes), "answers": ["counts", "expectations"] }, { "method": "trajectories", "accuracy": "estimate", "circuits": "any, with noise", "max_qubits": TRAJECTORY_MAX_QUBITS.min(statevector_max_qubits(job_bytes)), "answers": ["counts", "expectations"], "options": { "trajectories": "how many (default: the shots, or 1000 for expectations alone); shots that take the same errors share one state" } }, ]) } @@ -100,6 +106,23 @@ pub fn amplitude_bytes(n: u32) -> u64 { amps + amps / 32 } +/// The most qubits the density matrix holds in `job_bytes`: `ρ` is a state +/// vector on twice as many. +pub fn density_max_qubits(job_bytes: u64) -> u32 { + (1..=DENSITY_MAX_QUBITS).take_while(|&n| statevector_bytes(2 * n) <= job_bytes).last().unwrap_or(0) +} + +/// The most qubits the GPU's largest buffer holds, asked of the device once. +pub fn gpu_max_qubits() -> Result { + static MAX: std::sync::OnceLock> = std::sync::OnceLock::new(); + MAX.get_or_init(|| wai_quantum::quantum_sv_metal::GpuState::max_qubits().map_err(|e| e.to_string())).clone() +} + +/// The most qubits the single-precision state vector holds in `job_bytes`. +pub fn single_max_qubits(job_bytes: u64) -> u32 { + (3..=40u32).take_while(|&n| (8u64 << n) + 256 * MIB <= job_bytes).last().unwrap_or(0) +} + /// The most qubits the dense state vector holds in `job_bytes`. pub fn statevector_max_qubits(job_bytes: u64) -> u32 { (1..=wai_quantum::quantum_sv::MAX_QUBITS).rev().find(|&n| statevector_bytes(n) <= job_bytes).unwrap_or(0) @@ -245,7 +268,7 @@ fn try_method(method: &str, r: &Request, dyadic: &Result 0; let wants_amplitudes = !r.amplitudes.is_empty(); let wants_expectations = !r.observables.is_empty(); - if r.noise.is_some() && !matches!(method, "density_matrix" | "trajectories") { + if r.noise.is_some() && !matches!(method, "density_matrix" | "trajectories" | "stabilizer") { return Err(format!("{method} does not model noise")); } match method { @@ -266,6 +289,17 @@ fn try_method(method: &str, r: &Request, dyadic: &Result { + let max = gpu_max_qubits().map_err(|e| format!("statevector_single needs a GPU: {e}"))?; + let bytes = (8u64 << n.min(40)) + 256 * MIB; + if n > max || bytes > cap { + return Err(format!("statevector_single holds at most {} qubits on this GPU in a job's memory", max.min(single_max_qubits(cap)))); + } + if r.program.ops.iter().any(|o| o.qubits.len() > wai_quantum::quantum_sv_metal::MAX_WIDTH) { + return Err("statevector_single takes gates on at most four qubits".into()); + } + Ok(plan(bytes + r.shots * 24, gates << n, format!("dense single-precision state vector of {n} qubits on the GPU"))) + } "stabilizer" => { if let Err(e) = clifford { return Err(format!("stabilizer needs a Clifford circuit: {e}")); @@ -276,18 +310,35 @@ fn try_method(method: &str, r: &Request, dyadic: &Result 0.0) { + return Err("stabilizer models Pauli noise (depolarizing, dephasing, readout), not amplitude damping".into()); + } let (nn, measured) = (n as u64, r.program.measured.len() as u64); let words = nn.div_ceil(64); // The gates; the elimination for counts; each shot; each observable. - let work = gates * 2 * words + let mut work = gates * 2 * words + if counts { nn * nn * words + r.shots * nn * measured.div_ceil(64) } else { 0 } + r.observables.len() as u64 * 2 * nn * words; + if r.noise.is_some() { + // Pauli frames: every step on a word of 64 shots, and each + // observable's parity over the shots. + let lowered = clifford.as_ref().map_or(0, |c| c.len() as u64); + let steps = lowered + 3 * r.program.ops.len() as u64; + let frame_shots = r.shots.max(if wants_expectations { frame_expectation_shots(r) } else { 0 }); + work += steps * frame_shots.div_ceil(64) + r.observables.len() as u64 * frame_shots.div_ceil(64) * 4; + } if work > STABILIZER_MAX_WORK { return Err(format!("this Clifford job is more stabilizer work than one job may take ({work} word operations, at most {STABILIZER_MAX_WORK}); ask for fewer shots")); } // The tableau, its rows and the outcome subspace, about n² bytes // each; and the counts. - let memory = 3 * nn * nn / 2 + 64 * MIB + r.shots.min(1 << 20) * (r.program.n_bits as u64 / 8 + 64); + let mut memory = 3 * nn * nn / 2 + 64 * MIB + r.shots.min(1 << 20) * (r.program.n_bits as u64 / 8 + 64); + if r.noise.is_some() { + // Up to eight batches of frames at once, 8192 shots each, and + // their outcomes. + memory += 8 * (2 * nn * 128 * 8 + 8192 * (measured.div_ceil(64) * 8 + 64)); + return Ok(plan(memory, work, format!("noisy Clifford circuit: Pauli frames of {n} qubits against a stabilizer-tableau reference"))); + } Ok(plan(memory, work, format!("Clifford circuit: the stabilizer tableau of {n} qubits"))) } "pauli" => { @@ -321,10 +372,14 @@ fn try_method(method: &str, r: &Request, dyadic: &Result DENSITY_MAX_QUBITS || wants_amplitudes { - return Err(format!("density_matrix holds at most {DENSITY_MAX_QUBITS} qubits and answers counts and expectations")); + let most = density_max_qubits(cap); + if n > most || wants_amplitudes { + return Err(format!("density_matrix holds at most {most} qubits in a job's memory and answers counts and expectations")); } - Ok(plan((32u64 << (2 * n)) + 128 * MIB, gates << (2 * n), format!("density matrix of {n} qubits under the noise model"))) + if (gates << (2 * n)) > DENSITY_MAX_WORK { + return Err(format!("density_matrix on {n} qubits holds 4^{n} entries; {gates} gates of passes over them is more than one job may take")); + } + Ok(plan(statevector_bytes(2 * n), gates << (2 * n), format!("density matrix of {n} qubits under the noise model, each gate and its noise one superoperator pass"))) } "trajectories" => { if r.noise.is_none() { @@ -369,6 +424,13 @@ pub fn trajectory_states(n: u32, cap: u64) -> usize { ((cap.saturating_sub(256 * MIB) / 2 / (amplitude_bytes(n) * trees).max(1)) as usize).clamp(1, 16) } +/// Shots a noisy stabilizer job estimates expectation values from: the +/// `trajectories` option, or the job's shots, at least 10000 (frames are +/// cheap, so the default buys a small standard error). +pub fn frame_expectation_shots(r: &Request) -> u64 { + r.options.get("trajectories").and_then(Value::as_u64).unwrap_or(r.shots.max(10_000)).clamp(1, 10_000_000) +} + /// Trajectories: the option, or one a shot, or 1000 for expectations alone. pub fn trajectory_count(r: &Request) -> u64 { let default = if r.shots > 0 { r.shots } else { 1000 }; @@ -384,13 +446,13 @@ pub fn plan(v: &Value, job_bytes: u64) -> Result { return try_method(&r.method, &r, &dyadic, &clifford, job_bytes); } let order: &[&str] = if r.noise.is_some() { - &["density_matrix", "trajectories"] + &["density_matrix", "stabilizer", "trajectories"] } else if r.shots > 0 { - &["stabilizer", "exact", "statevector", "mps"] + &["stabilizer", "exact", "statevector", "statevector_single", "mps"] } else if !r.observables.is_empty() && r.amplitudes.is_empty() { - &["stabilizer", "exact", "statevector", "pauli", "mps"] + &["stabilizer", "exact", "statevector", "statevector_single", "pauli", "mps"] } else { - &["exact", "statevector", "tensor_network", "mps"] + &["exact", "statevector", "statevector_single", "tensor_network", "mps"] }; let mut refusals = Vec::new(); for m in order { diff --git a/wai/wai-quantum-service/src/discovery.rs b/wai/wai-quantum-service/src/discovery.rs new file mode 100644 index 000000000..1027004b2 --- /dev/null +++ b/wai/wai-quantum-service/src/discovery.rs @@ -0,0 +1,222 @@ +//! What a newcomer, person or program, needs to start: worked examples, an +//! OpenAPI description of the API, and an index at the root. + +use serde_json::{Value, json}; + +/// A chain of `n` qubits: `body(i)` for each qubit, as OpenQASM lines. +fn lines(n: usize, body: impl Fn(usize) -> String) -> String { + (0..n).map(body).collect::>().join("\n") +} + +/// Ready-to-run jobs, each with what it shows. Every one runs as given. +pub fn examples() -> Value { + let ghz = |n: usize| format!("OPENQASM 3.0;\nqubit[{n}] q;\nbit[{n}] c;\nh q[0];\n{}\nc = measure q;", lines(n - 1, |i| format!("cx q[{i}], q[{}];", i + 1))); + let ring_qaoa = { + let n = 6; + let mut s = format!("OPENQASM 3.0;\nqubit[{n}] q;\nbit[{n}] c;\nh q;\n"); + s += &lines(n, |i| format!("rzz(0.8) q[{i}], q[{}];", (i + 1) % n)); + s += "\nrx(0.7) q;\nc = measure q;"; + s + }; + let trotter = |n: usize, steps: usize| { + let mut s = format!("OPENQASM 3.0;\nqubit[{n}] q;\n"); + for _ in 0..steps { + s += &format!("rx(0.6) q;\n{}\n", lines(n - 1, |i| format!("rzz(-1.5707963267948966) q[{i}], q[{}];", i + 1))); + } + s + }; + let brick = |n: usize, layers: usize| { + let mut s = format!("OPENQASM 3.0;\nqubit[{n}] q;\nbit[{n}] c;\n"); + for l in 0..layers { + s += &format!("ry(0.3) q;\nrz(0.2) q;\n{}\n", lines(n / 2 - usize::from(l % 2 == 1), |i| format!("cx q[{}], q[{}];", 2 * i + l % 2, 2 * i + l % 2 + 1))); + } + s += "c = measure q;"; + s + }; + json!([ + { + "name": "bell", + "title": "A Bell pair", + "shows": "Two qubits entangled: half the shots read 00, half 11, never 01 or 10. The service sees a Clifford circuit and uses the stabilizer tableau, exactly.", + "job": { "qasm": ghz(2), "shots": 1000, "observables": ["Z0 Z1", "X0 X1"] } + }, + { + "name": "rotation", + "title": "A rotation by any angle", + "shows": "rx(0.3) on one qubit: ⟨Z⟩ = cos 0.3 = 0.9553, ⟨Y⟩ = −sin 0.3. A continuous angle takes the double-precision state vector.", + "job": { "qasm": "OPENQASM 3.0;\nqubit[1] q;\nrx(0.3) q[0];", "observables": ["Z0", "Y0"], "amplitudes": ["0", "1"] } + }, + { + "name": "qaoa-ring", + "title": "QAOA on a six-node ring", + "shows": "One layer of the quantum approximate optimisation algorithm for max-cut on a ring: cost terms ZZ on each edge, then a mixer. The counts favour cuts; the ZZ expectations score them.", + "job": { "qasm": ring_qaoa, "shots": 2000, "observables": ["Z0 Z1", "Z1 Z2", "Z2 Z3", "Z3 Z4", "Z4 Z5", "Z5 Z0"] } + }, + { + "name": "noisy-bell", + "title": "A Bell pair on a noisy device", + "shows": "The same pair with gate errors and readout flips: 01 and 10 appear, and ⟨Z0 Z1⟩ falls below 1. With noise on few qubits the exact density matrix runs.", + "job": { "qasm": ghz(2), "shots": 4000, "observables": ["Z0 Z1"], "noise": { "depolarizing_1q": 0.01, "depolarizing_2q": 0.03, "readout": 0.02 } } + }, + { + "name": "ghz-1000", + "title": "A thousand-qubit GHZ state", + "shows": "Far past any state vector: a Clifford circuit on 1000 qubits, sampled exactly by the stabilizer tableau. Every shot is all zeros or all ones.", + "job": { "qasm": ghz(1000), "shots": 100, "observables": ["Z0 Z999"] } + }, + { + "name": "noisy-ghz-500", + "title": "A noisy 500-qubit GHZ state", + "shows": "Pauli noise on a large Clifford circuit, sampled by Pauli frames: ⟨Z0 Z499⟩ decays with every gate between them.", + "job": { "qasm": ghz(500), "shots": 1000, "observables": ["Z0 Z499"], "noise": { "depolarizing_2q": 0.0005, "readout": 0.001 } } + }, + { + "name": "chain-100-dynamics", + "title": "Quench dynamics on 100 qubits", + "shows": "Four Trotter steps of a transverse-field Ising chain. Expectation values only, so sparse Pauli dynamics runs: each value comes with a bound on its error, and a ladder of thresholds shows it settling.", + "job": { "qasm": trotter(100, 4), "observables": ["Z50", "Z49 Z50"] } + }, + { + "name": "mps-200", + "title": "200 qubits at low entanglement", + "shows": "Shallow brickwork on 200 qubits with any angles: a matrix product state holds it, and says whether anything was truncated.", + "job": { "qasm": brick(200, 4), "shots": 200, "observables": ["Z100"] } + } + ]) +} + +/// The API as an OpenAPI 3.1 document. +pub fn openapi(server: &str) -> Value { + let pauli = json!({ "oneOf": [ + { "type": "string", "description": "A Pauli string: factors such as `Z0`, `X3`, `Y12`, separated by spaces; qubit indices count from 0.", "examples": ["Z0 Z1", "X3 Y4"] }, + { "type": "object", "required": ["pauli"], "properties": { "pauli": { "type": "string" }, "coeff": { "type": "number", "default": 1.0 } } } + ]}); + let job_request = json!({ + "type": "object", + "required": ["qasm"], + "properties": { + "qasm": { "type": "string", "description": "The program: OpenQASM 3, or OpenQASM 2. Registers, standard gates (h, x, y, z, s, sdg, t, tdg, sx, rx, ry, rz, p, u, cx, cy, cz, ch, swap, iswap, cp, crx, cry, crz, ccx, ccz, cswap, rxx, ryy, rzz), the control modifiers ctrl @, ctrl(n) @ and negctrl @ on x y z h s sdg t tdg p and the controlled names, user gate definitions and terminal measurement. Mid-circuit measurement, classical control, inv @ and pow @ are refused, with the line." }, + "method": { "type": "string", "enum": ["auto", "exact", "statevector", "statevector_single", "stabilizer", "pauli", "mps", "tensor_network", "density_matrix", "trajectories"], "default": "auto" }, + "shots": { "type": "integer", "minimum": 0, "maximum": 1000000, "description": "Measurement shots (default 1024 when nothing else is asked for)." }, + "seed": { "type": "integer", "default": 1, "description": "The sampling seed: the same job and seed give the same counts." }, + "observables": { "type": "array", "items": pauli }, + "amplitudes": { "type": "array", "items": { "type": "string", "pattern": "^[01]+$" }, "description": "Bit strings, one character per qubit, qubit n−1 first." }, + "noise": { "type": "object", "properties": { + "depolarizing_1q": { "type": "number", "minimum": 0, "maximum": 1 }, + "depolarizing_2q": { "type": "number", "minimum": 0, "maximum": 1 }, + "amplitude_damping": { "type": "number", "minimum": 0, "maximum": 1 }, + "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." } + } + }); + let job = json!({ + "type": "object", + "properties": { + "id": { "type": "string" }, + "status": { "type": "string", "enum": ["queued", "running", "done", "failed", "cancelled"] }, + "method": { "type": "string" }, + "why": { "type": "string", "description": "Why this method: what it is, and what the circuit is." }, + "request": { "$ref": "#/components/schemas/JobRequest", "description": "The request as submitted: the receipt is checked against it." }, + "result": { "type": "object", "description": "counts (highest classical bit first), expectations, amplitudes, accuracy, elapsed_ms and a receipt." }, + "error": { "type": "string" } + } + }); + let err = json!({ "type": "object", "properties": { "error": { "type": "string" } } }); + json!({ + "openapi": "3.1.0", + "info": { + "title": "WAI quantum service", + "version": env!("CARGO_PKG_VERSION"), + "summary": "Run quantum programs: counts, expectation values and amplitudes, each with the method that produced it, its accuracy and a signed receipt. No account and no key.", + "license": { "name": "Apache-2.0" } + }, + "servers": [{ "url": server }], + "components": { "schemas": { "JobRequest": job_request, "Job": job, "Error": err } }, + "paths": { + "/v1": { "get": { "summary": "The service: methods, limits, load and receipt key", "responses": { "200": { "description": "The service" } } } }, + "/v1/methods": { "get": { "summary": "The methods and what each takes", "responses": { "200": { "description": "The methods" } } } }, + "/v1/examples": { "get": { "summary": "Ready-to-run example jobs", "responses": { "200": { "description": "The examples" } } } }, + "/v1/run": { "post": { + "summary": "Submit a job and wait up to 25 s for it", + "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobRequest" } } } }, + "responses": { + "200": { "description": "The finished job", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Job" } } } }, + "202": { "description": "Still running: poll GET /v1/jobs/{id}", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Job" } } } }, + "400": { "description": "A program or job that does not read", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, + "422": { "description": "Larger than one job may be" }, + "429": { "description": "Past the per-client limits" }, + "503": { "description": "The queue is full" } + } + } }, + "/v1/jobs": { "post": { + "summary": "Submit a job", + "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobRequest" } } } }, + "responses": { "202": { "description": "Accepted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Job" } } } } } + } }, + "/v1/jobs/{id}": { + "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], + "get": { "summary": "A job: its status, and its result when done", "responses": { "200": { "description": "The job", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Job" } } } }, "404": { "description": "No such job" } } }, + "delete": { "summary": "Cancel a queued or running job", "responses": { "200": { "description": "Cancelled" }, "409": { "description": "Already finished" } } } + }, + "/v1/verify": { "post": { + "summary": "Whether a result's receipt seals that request and that result", + "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["request", "result"], "properties": { "request": { "$ref": "#/components/schemas/JobRequest" }, "result": { "type": "object" } } } } } }, + "responses": { "200": { "description": "{\"valid\": bool}" } } + } }, + "/mcp": { "post": { "summary": "The Model Context Protocol endpoint (streamable HTTP, JSON responses): tools to run circuits, read jobs, verify receipts and list examples", "responses": { "200": { "description": "A JSON-RPC response" } } } } + } + }) +} + +/// Where everything is, for a program that lands on the root. +pub fn index(base: &str, console: &str) -> Value { + json!({ + "service": "wai-quantum-service", + "summary": "Run quantum programs (OpenQASM 3 or 2). No account and no key.", + "console": console, + "api": format!("{base}/v1"), + "run": format!("POST {base}/v1/run"), + "examples": format!("{base}/v1/examples"), + "openapi": format!("{base}/v1/openapi.json"), + "mcp": format!("{base}/mcp"), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Every example plans, with the method its description promises. + #[test] + fn every_example_plans_as_described() { + let want = [ + ("bell", "stabilizer"), + ("rotation", "statevector"), + ("qaoa-ring", "statevector"), + ("noisy-bell", "density_matrix"), + ("ghz-1000", "stabilizer"), + ("noisy-ghz-500", "stabilizer"), + ("chain-100-dynamics", "pauli"), + ("mps-200", "mps"), + ]; + let ex = examples(); + let ex = ex.as_array().unwrap(); + assert_eq!(ex.len(), want.len()); + for (e, (name, method)) in ex.iter().zip(want) { + assert_eq!(e["name"], name); + let plan = crate::catalog::plan(&e["job"], 72 << 30).unwrap_or_else(|why| panic!("{name}: {why}")); + assert_eq!(plan.method, method, "{name}: {}", plan.why); + } + } + + #[test] + fn the_openapi_document_names_every_route() { + let d = openapi("https://example.org/q"); + for p in ["/v1", "/v1/methods", "/v1/examples", "/v1/run", "/v1/jobs", "/v1/jobs/{id}", "/v1/verify", "/mcp"] { + assert!(d["paths"].get(p).is_some(), "{p}"); + } + assert_eq!(d["openapi"], "3.1.0"); + } +} diff --git a/wai/wai-quantum-service/src/http.rs b/wai/wai-quantum-service/src/http.rs index 6482bdf6e..3fa34dd94 100644 --- a/wai/wai-quantum-service/src/http.rs +++ b/wai/wai-quantum-service/src/http.rs @@ -10,6 +10,10 @@ //! POST /v1/run submit and wait (up to 25 s): the finished job, //! or 202 and the job if it is still running //! POST /v1/verify a receipt checked against its request and result +//! GET /v1/examples ready-to-run example jobs +//! GET /v1/openapi.json this API as an OpenAPI 3.1 document +//! POST /mcp the Model Context Protocol endpoint (see `mcp`) +//! GET / an index (a browser is sent to the console page) //! ``` //! //! A job is `{"qasm": "", "method": "auto", "shots": N, @@ -40,6 +44,10 @@ pub struct App { pub signer: Arc, /// Peers whose `X-Forwarded-For` names the client: the front proxy. pub trusted: Vec, + /// The service's public base URL, for links it hands out. + pub base: String, + /// The page people use the service from. + pub console: String, } type Shared = Arc; @@ -89,7 +97,9 @@ async fn describe(State(app): State) -> Response { "load": { "queued": queued, "running": running, "memory_held_bytes": held << 20 }, "uptime_seconds": app.service.started.elapsed().map(|d| d.as_secs()).unwrap_or(0), "receipt_key": app.signer.public_key(), - "endpoints": ["GET /v1", "GET /v1/health", "GET /v1/methods", "POST /v1/jobs", "GET /v1/jobs/{id}", "DELETE /v1/jobs/{id}", "POST /v1/run", "POST /v1/verify"], + "endpoints": ["GET /v1", "GET /v1/health", "GET /v1/methods", "GET /v1/examples", "GET /v1/openapi.json", "POST /v1/jobs", "GET /v1/jobs/{id}", "DELETE /v1/jobs/{id}", "POST /v1/run", "POST /v1/verify", "POST /mcp"], + "mcp": format!("{}/mcp", app.base), + "console": app.console, }), ) } @@ -104,7 +114,10 @@ async fn health() -> Response { /// Plans and admits `request` for `who`: the job's id, or the refusal's /// status and reason. -fn admit(app: &App, who: &str, request: Value) -> Result { +pub(crate) fn admit(app: &App, who: &str, request: Value) -> Result { + if let Some(at) = crate::receipt::inexact_number(&request) { + return Err((400, format!("`{at}`: a whole number here must be within ±9007199254740991 (2^53 − 1), so that every JSON reader reads it alike and the receipt binds it"))); + } let plan = catalog::plan(&request, app.service.config.job_mib << 20).map_err(|e| (400, e))?; app.service.submit(who, request, plan).map_err(|Refused(code, why)| (code, why)) } @@ -169,12 +182,58 @@ async fn verify(body: Result, axum::extract::rejection::JsonRejectio reply(200, json!({ "valid": receipt::verify(request, &bare, receipt), "key": receipt.get("key") })) } +async fn examples() -> Response { + reply(200, json!({ "examples": crate::discovery::examples() })) +} + +async fn openapi(State(app): State) -> Response { + reply(200, crate::discovery::openapi(&app.base)) +} + +/// The root: a browser goes to the console page; a program gets an index. +async fn index(State(app): State, headers: HeaderMap) -> Response { + let html = headers.get("accept").and_then(|v| v.to_str().ok()).is_some_and(|a| a.contains("text/html")); + if html && let Ok(to) = HeaderValue::from_str(&app.console) { + let mut r = StatusCode::FOUND.into_response(); + r.headers_mut().insert("location", to); + return r; + } + reply(200, crate::discovery::index(&app.base, &app.console)) +} + +/// One JSON-RPC message (MCP streamable HTTP, answered as JSON): a request +/// gets its response; a notification gets 202 and no body. +async fn mcp(State(app): State, ConnectInfo(peer): ConnectInfo, headers: HeaderMap, body: Result, axum::extract::rejection::JsonRejection>) -> Response { + let Ok(Json(msg)) = body else { + return reply(400, json!({ "jsonrpc": "2.0", "id": null, "error": { "code": -32700, "message": "not JSON" } })); + }; + if !msg.is_object() { + return reply(400, json!({ "jsonrpc": "2.0", "id": null, "error": { "code": -32600, "message": "one JSON-RPC message a request" } })); + } + let who = client(&app, peer, &headers); + match crate::mcp::handle(&app, &who, &msg, &app.base).await { + Some(answer) => reply(200, answer), + None => { + let mut r = StatusCode::ACCEPTED.into_response(); + r.headers_mut().insert("access-control-allow-origin", HeaderValue::from_static("*")); + r + } + } +} + +/// No event stream is offered: `GET /mcp` is 405, as the protocol allows. +async fn mcp_get() -> Response { + let mut r = error(405, "this endpoint takes POST; it offers no event stream"); + r.headers_mut().insert("allow", HeaderValue::from_static("POST")); + r +} + async fn preflight() -> Response { let mut r = StatusCode::NO_CONTENT.into_response(); let h = r.headers_mut(); h.insert("access-control-allow-origin", HeaderValue::from_static("*")); h.insert("access-control-allow-methods", HeaderValue::from_static("GET, POST, DELETE, OPTIONS")); - h.insert("access-control-allow-headers", HeaderValue::from_static("content-type")); + h.insert("access-control-allow-headers", HeaderValue::from_static("content-type, accept, mcp-protocol-version, mcp-session-id")); h.insert("access-control-max-age", HeaderValue::from_static("86400")); r } @@ -188,6 +247,10 @@ pub fn router(app: Shared) -> Router { .route("/v1/jobs/{id}", get(job).delete(cancel)) .route("/v1/run", post(run)) .route("/v1/verify", post(verify)) + .route("/v1/examples", get(examples)) + .route("/v1/openapi.json", get(openapi)) + .route("/mcp", post(mcp).get(mcp_get)) + .route("/", get(index)) .route_layer(axum::middleware::from_fn(|req: axum::extract::Request, next: axum::middleware::Next| async move { if req.method() == Method::OPTIONS { preflight().await } else { next.run(req).await } })) diff --git a/wai/wai-quantum-service/src/jobs.rs b/wai/wai-quantum-service/src/jobs.rs index 7e9655a19..c076d9567 100644 --- a/wai/wai-quantum-service/src/jobs.rs +++ b/wai/wai-quantum-service/src/jobs.rs @@ -138,6 +138,9 @@ impl Job { "method": self.plan.method, "why": self.plan.why, "estimate": { "memory_bytes": self.plan.memory_bytes, "work": self.plan.work }, + // The request as submitted, so whoever holds the id can check the + // result's receipt against it. + "request": self.request, "submitted_ms": unix_ms(self.submitted), }); if let Some(t) = self.started { diff --git a/wai/wai-quantum-service/src/main.rs b/wai/wai-quantum-service/src/main.rs index 36becb551..ca6129ab9 100644 --- a/wai/wai-quantum-service/src/main.rs +++ b/wai/wai-quantum-service/src/main.rs @@ -14,8 +14,10 @@ //! ``` mod catalog; +mod discovery; mod http; mod jobs; +mod mcp; mod qasm; mod receipt; mod run; @@ -99,7 +101,10 @@ fn serve() { let signer = Arc::new(signer); service.seal_with(Arc::clone(&signer)); tokio::spawn(Arc::clone(&service).schedule()); - let app = Arc::new(http::App { service, signer, trusted }); + // Where the service is seen from outside, and its page for people. + let base = std::env::var("WAI_QSVC_PUBLIC_BASE").unwrap_or_else(|_| "https://wai.transaction.science/quantum-jobs".into()); + let console = std::env::var("WAI_QSVC_CONSOLE").unwrap_or_else(|_| "https://wai.transaction.science/quantum-run/".into()); + let app = Arc::new(http::App { service, signer, trusted, base, console }); let listener = tokio::net::TcpListener::bind(addr).await.unwrap_or_else(|e| { eprintln!("binding {addr}: {e}"); std::process::exit(1) diff --git a/wai/wai-quantum-service/src/mcp.rs b/wai/wai-quantum-service/src/mcp.rs new file mode 100644 index 000000000..5906d62dd --- /dev/null +++ b/wai/wai-quantum-service/src/mcp.rs @@ -0,0 +1,294 @@ +//! The service as Model Context Protocol tools: `POST /mcp`, streamable HTTP +//! answered with plain JSON, no session and no key. An agent adds the URL and +//! gets five tools: run a circuit, read a job, verify a receipt, describe the +//! service, and list worked examples. +//! +//! Each tool result carries a short text summary written for a model to read +//! (method, why, accuracy, the leading counts, the expectation values) and +//! the whole job as structured content. + +use crate::catalog; +use crate::discovery; +use crate::http::App; +use crate::receipt; +use serde_json::{Value, json}; +use std::time::Duration; + +/// Protocol versions this server speaks, newest first. +pub const VERSIONS: [&str; 3] = ["2025-11-25", "2025-06-18", "2025-03-26"]; + +/// How long a `run_quantum_circuit` call waits before handing back the job id. +pub const CALL_WAIT: Duration = Duration::from_secs(50); + +const INSTRUCTIONS: &str = "Run quantum programs written in OpenQASM 3 (or 2). Call run_quantum_circuit with the program and what you want back: shots for measurement counts, observables (Pauli strings such as \"Z0 Z1\") for expectation values, amplitudes (bit strings, qubit n-1 first) for amplitudes. The service picks the strongest method the circuit allows and says which and why; every result names its accuracy and carries a signature verify_quantum_receipt can check. Counts are keyed by the classical bits, the highest bit first. If a call answers with status \"running\", call get_quantum_job with its id. quantum_circuit_examples gives ready-to-run programs."; + +fn tools() -> Value { + let job_props = json!({ + "qasm": { "type": "string", "description": "The program, OpenQASM 3 or 2. Supported: qubit/bit registers and broadcast over them; gates h x y z s sdg t tdg sx sxdg rx ry rz p u cx cy cz ch swap iswap cp crx cry crz cu ccx ccz cswap rxx ryy rzz; the control modifiers `ctrl @`, `ctrl(n) @`, `negctrl @` on x y z h s sdg t tdg p and the controlled names; user `gate` definitions; angle expressions with pi; `reset` before a qubit's first gate; terminal measurement (`c = measure q;`). Not supported: mid-circuit measurement or reset, classical control, `inv @`, `pow @`." }, + "shots": { "type": "integer", "minimum": 0, "maximum": 1000000, "description": "Measurement shots. Default 1024 when no observables or amplitudes are asked for." }, + "observables": { "type": "array", "items": { "type": "string" }, "description": "Pauli strings to take expectation values of, e.g. [\"Z0\", \"Z0 Z1\", \"X2 Y3\"]. Qubits count from 0." }, + "amplitudes": { "type": "array", "items": { "type": "string" }, "description": "Basis states to give amplitudes of: one character per qubit, qubit n-1 first, e.g. \"0011\"." }, + "noise": { "type": "object", "description": "Optional noise, each a probability: depolarizing_1q, depolarizing_2q, amplitude_damping, dephasing (after each gate on its qubits), readout (each measured bit).", "properties": { + "depolarizing_1q": { "type": "number" }, "depolarizing_2q": { "type": "number" }, "amplitude_damping": { "type": "number" }, "dephasing": { "type": "number" }, "readout": { "type": "number" } + } }, + "method": { "type": "string", "enum": ["auto", "exact", "statevector", "statevector_single", "stabilizer", "pauli", "mps", "tensor_network", "density_matrix", "trajectories"], "description": "Leave as auto unless you need a particular method." }, + "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." } + }); + json!([ + { + "name": "run_quantum_circuit", + "title": "Run a quantum circuit", + "description": "Run an OpenQASM program and return measurement counts, Pauli expectation values and/or amplitudes. The service chooses the method (bit-exact or double-precision state vector up to 32 qubits, single precision on a GPU to 33, stabilizer tableau for Clifford circuits up to 32768 qubits, matrix product states up to 4096, sparse Pauli dynamics for expectation values up to 1024 with an error bound, an exact density matrix or trajectories for noise), reports why and how accurate, and signs the result. Waits up to 50 s; a longer job answers with status running and an id for get_quantum_job.", + "inputSchema": { "type": "object", "required": ["qasm"], "properties": job_props }, + "annotations": { "readOnlyHint": false, "idempotentHint": true, "openWorldHint": false } + }, + { + "name": "get_quantum_job", + "title": "Read a quantum job", + "description": "A job's status, and its result when it is done. Results are kept for a day.", + "inputSchema": { "type": "object", "required": ["id"], "properties": { "id": { "type": "string" } } }, + "annotations": { "readOnlyHint": true } + }, + { + "name": "verify_quantum_receipt", + "title": "Verify a result's receipt", + "description": "Whether a result's receipt (an Ed25519 signature by the service) seals exactly that request and that result. Pass the request as submitted and the result as returned.", + "inputSchema": { "type": "object", "required": ["request", "result"], "properties": { "request": { "type": "object" }, "result": { "type": "object" } } }, + "annotations": { "readOnlyHint": true } + }, + { + "name": "quantum_service_info", + "title": "What the quantum service can do", + "description": "The methods with their qubit limits and what each answers, the accuracy classes, the per-client limits, the current load, and the conventions (bit order, Pauli strings).", + "inputSchema": { "type": "object", "properties": {} }, + "annotations": { "readOnlyHint": true } + }, + { + "name": "quantum_circuit_examples", + "title": "Ready-to-run quantum examples", + "description": "Worked examples, each a complete job (OpenQASM program and arguments) with what it shows: a Bell pair, a rotation, QAOA, noise, a 1000-qubit GHZ state, 100-qubit dynamics, 200 qubits as a matrix product state. Pass an example's job to run_quantum_circuit as it is, or adapt it.", + "inputSchema": { "type": "object", "properties": {} }, + "annotations": { "readOnlyHint": true } + } + ]) +} + +/// A bit string short enough to read: as it is up to 48 bits; past that, as +/// runs (`0×1000`, `0×3 1×997`), or its two ends if the runs are many. +pub fn compact_bits(b: &str) -> String { + if b.len() <= 48 { + return b.to_string(); + } + let mut runs: Vec<(char, usize)> = Vec::new(); + for c in b.chars() { + match runs.last_mut() { + Some((d, n)) if *d == c => *n += 1, + _ => runs.push((c, 1)), + } + } + if runs.len() <= 6 { + return runs.iter().map(|(c, n)| format!("{c}×{n}")).collect::>().join(" "); + } + format!("{}…{} ({} bits)", &b[..16], &b[b.len() - 16..], b.len()) +} + +/// A job as a model should read it: a few lines, the leading outcomes first. +pub fn summary(job: &Value) -> String { + let mut s = Vec::new(); + let status = job["status"].as_str().unwrap_or("?"); + s.push(format!("status: {status} · job {}", job["id"].as_str().unwrap_or("?"))); + if let Some(m) = job["method"].as_str() { + s.push(format!("method: {m} — {}", job["why"].as_str().unwrap_or(""))); + } + if let Some(e) = job["error"].as_str() { + s.push(format!("error: {e}")); + } + let r = &job["result"]; + if r.is_object() { + let acc = &r["accuracy"]; + let mut a = format!("accuracy: {}", acc["class"].as_str().unwrap_or("?")); + for k in ["fidelity_estimate", "discarded_weight", "threshold", "trajectories", "histories", "device"] { + if let Some(v) = acc.get(k) { + a += &format!(", {k} {v}"); + } + } + s.push(a); + if let Some(w) = r["warning"].as_str() { + s.push(format!("warning: {w}")); + } + if let Some(c) = r["counts"].as_object() { + let total: u64 = c.values().filter_map(Value::as_u64).sum::() + r["omitted_shots"].as_u64().unwrap_or(0); + let mut v: Vec<(&String, u64)> = c.iter().filter_map(|(k, n)| n.as_u64().map(|n| (k, n))).collect(); + v.sort_by(|x, y| y.1.cmp(&x.1).then(x.0.cmp(y.0))); + let shown: Vec = v.iter().take(16).map(|(k, n)| format!("{}: {n} ({:.1}%)", compact_bits(k), 100.0 * *n as f64 / total.max(1) as f64)).collect(); + let more = if v.len() > 16 { format!(" … and {} more outcomes", v.len() - 16) } else { String::new() }; + s.push(format!("counts ({total} shots, highest classical bit first): {}{more}", shown.join(", "))); + } + if let Some(es) = r["expectations"].as_array() { + let parts: Vec = es + .iter() + .map(|e| { + let mut p = format!("⟨{}⟩ = {:.6}", e["observable"].as_str().unwrap_or("?"), e["value"].as_f64().unwrap_or(f64::NAN)); + if let Some(b) = e["bound"].as_f64() { + p += &format!(" (error at most {b:.2e})"); + } + if let Some(se) = e["stderr"].as_f64() { + p += &format!(" ± {se:.2e}"); + } + p + }) + .collect(); + s.push(format!("expectations: {}", parts.join("; "))); + } + if let Some(am) = r["amplitudes"].as_array() { + let parts: Vec = am.iter().map(|a| format!("{}: {:.6}{:+.6}i", compact_bits(a["bits"].as_str().unwrap_or("?")), a["re"].as_f64().unwrap_or(f64::NAN), a["im"].as_f64().unwrap_or(f64::NAN))).collect(); + s.push(format!("amplitudes: {}", parts.join("; "))); + } + if let Some(ms) = r["elapsed_ms"].as_u64() { + s.push(format!("ran in {ms} ms; signed receipt attached (verify_quantum_receipt checks it)")); + } + } else if status == "queued" || status == "running" { + s.push("still running: call get_quantum_job with this id".into()); + } + s.join("\n") +} + +fn ok(id: &Value, result: Value) -> Value { + json!({ "jsonrpc": "2.0", "id": id, "result": result }) +} + +fn fail(id: &Value, code: i64, message: &str) -> Value { + json!({ "jsonrpc": "2.0", "id": id, "error": { "code": code, "message": message } }) +} + +/// A tool's answer: text for the model, the whole object as structured content. +fn tool_result(text: String, structured: Value, is_error: bool) -> Value { + json!({ "content": [{ "type": "text", "text": text }], "structuredContent": structured, "isError": is_error }) +} + +/// Handle one JSON-RPC message from `who`. `None` for a notification, which +/// gets no answer. +pub async fn handle(app: &App, who: &str, msg: &Value, base: &str) -> Option { + let id = msg.get("id")?.clone(); + let Some(method) = msg["method"].as_str() else { + return Some(fail(&id, -32600, "not a JSON-RPC request")); + }; + let params = &msg["params"]; + Some(match method { + "initialize" => { + let asked = params["protocolVersion"].as_str().unwrap_or(""); + let version = VERSIONS.iter().find(|v| **v == asked).copied().unwrap_or(VERSIONS[0]); + ok( + &id, + json!({ + "protocolVersion": version, + "capabilities": { "tools": { "listChanged": false } }, + "serverInfo": { "name": "wai-quantum", "title": "WAI quantum service", "version": env!("CARGO_PKG_VERSION") }, + "instructions": INSTRUCTIONS, + }), + ) + } + "ping" => ok(&id, json!({})), + "tools/list" => ok(&id, json!({ "tools": tools() })), + "tools/call" => { + let args = params.get("arguments").cloned().unwrap_or(json!({})); + ok(&id, call(app, who, params["name"].as_str().unwrap_or(""), args, base).await) + } + other => fail(&id, -32601, &format!("no method {other}; this server offers initialize, ping, tools/list and tools/call")), + }) +} + +async fn call(app: &App, who: &str, name: &str, args: Value, base: &str) -> Value { + match name { + "run_quantum_circuit" => { + let id = match crate::http::admit(app, who, args) { + Ok(id) => id, + Err((_, why)) => return tool_result(format!("refused: {why}"), json!({ "error": why }), true), + }; + app.service.wait(&id, CALL_WAIT).await; + let job = app.service.get(&id).unwrap_or(json!({ "id": id })); + let failed = job["status"] == "failed"; + tool_result(summary(&job), job, failed) + } + "get_quantum_job" => match args["id"].as_str().and_then(|i| app.service.get(i)) { + Some(job) => tool_result(summary(&job), job, false), + None => tool_result("no such job (results are kept for a day)".into(), json!({ "error": "no such job" }), true), + }, + "verify_quantum_receipt" => { + let (request, result) = (&args["request"], &args["result"]); + let Some(r) = result.get("receipt") else { + return tool_result("the result carries no receipt".into(), json!({ "valid": false }), true); + }; + let mut bare = result.clone(); + if let Some(o) = bare.as_object_mut() { + o.remove("receipt"); + } + let valid = receipt::verify(request, &bare, r); + tool_result(if valid { "valid: the receipt seals exactly this request and this result".into() } else { "not valid: the request, the result or the receipt was changed".into() }, json!({ "valid": valid, "key": r.get("key") }), false) + } + "quantum_service_info" => { + let c = &app.service.config; + let (queued, running, _) = app.service.load(); + let info = json!({ + "methods": catalog::describe(c.job_mib << 20), + "accuracy_classes": catalog::accuracy_classes(), + "conventions": { + "counts": "keyed by the program's classical bits, the highest bit first; with no measurement, every qubit is measured into the bit of its own index", + "amplitudes": "bit strings with one character per qubit, qubit n-1 first", + "observables": "Pauli strings such as \"Z0 Z1\" or {\"pauli\": \"X3\", \"coeff\": 0.5}; qubits count from 0" + }, + "limits": { "job_seconds": c.job_timeout.as_secs(), "active_jobs_per_client": c.per_client_active, "submissions_per_window": c.per_client_burst, "window_seconds": c.per_client_window.as_secs() }, + "load": { "queued": queued, "running": running }, + "api": discovery::index(base, &app.console), + }); + let lines: Vec = info["methods"] + .as_array() + .into_iter() + .flatten() + .map(|m| format!("{} — up to {} qubits, {}; {}", m["method"].as_str().unwrap_or("?"), m["max_qubits"], m["accuracy"].as_str().unwrap_or(""), m["circuits"].as_str().unwrap_or(""))) + .collect(); + tool_result(format!("methods (auto picks the first that fits):\n{}\ncounts: highest classical bit first; amplitudes: qubit n-1 first", lines.join("\n")), info, false) + } + "quantum_circuit_examples" => { + let ex = discovery::examples(); + let lines: Vec = ex.as_array().into_iter().flatten().map(|e| format!("{} — {}: {}", e["name"].as_str().unwrap_or(""), e["title"].as_str().unwrap_or(""), e["shows"].as_str().unwrap_or(""))).collect(); + tool_result(format!("{}\n(each example's `job` in the structured content runs as given)", lines.join("\n")), json!({ "examples": ex }), false) + } + other => tool_result(format!("no tool {other}"), json!({ "error": "no such tool" }), true), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn the_summary_reads_a_finished_job() { + let job = json!({ + "id": "abc", "status": "done", "method": "stabilizer", "why": "Clifford circuit", + "result": { "accuracy": { "class": "exact" }, "counts": { "00": 503, "11": 497 }, "expectations": [{ "observable": "Z0 Z1", "value": 1.0 }], "elapsed_ms": 3 } + }); + let s = summary(&job); + assert!(s.contains("method: stabilizer"), "{s}"); + assert!(s.contains("00: 503 (50.3%)") && s.contains("11: 497"), "{s}"); + assert!(s.contains("⟨Z0 Z1⟩ = 1.000000"), "{s}"); + } + + #[test] + fn wide_outcomes_are_written_short() { + assert_eq!(compact_bits("0110"), "0110"); + assert_eq!(compact_bits(&"0".repeat(1000)), "0×1000"); + assert_eq!(compact_bits(&format!("{}{}", "1".repeat(3), "0".repeat(97))), "1×3 0×97"); + let alt: String = (0..100).map(|i| if i % 2 == 0 { '0' } else { '1' }).collect(); + assert_eq!(compact_bits(&alt), format!("{}…{} (100 bits)", &alt[..16], &alt[84..])); + } + + #[test] + fn every_tool_has_a_schema_and_a_description() { + for t in tools().as_array().unwrap() { + assert!(t["description"].as_str().unwrap().len() > 40, "{t}"); + assert_eq!(t["inputSchema"]["type"], "object", "{t}"); + } + } +} diff --git a/wai/wai-quantum-service/src/qasm.rs b/wai/wai-quantum-service/src/qasm.rs index 058ed05b6..456d6d574 100644 --- a/wai/wai-quantum-service/src/qasm.rs +++ b/wai/wai-quantum-service/src/qasm.rs @@ -11,8 +11,14 @@ //! `c = measure q;`, `measure q -> c;`, `measure q;`). With no measurement, //! every qubit is measured into a bit of its own index. //! -//! Refused, each with its line: classical control, gate modifiers, a gate on a -//! qubit after its measurement, and anything else not listed. +//! Control modifiers, `ctrl @`, `ctrl(n) @`, `negctrl @` and `negctrl(n) @`, +//! on `x y z h s sdg t tdg p` and on the controlled names `cx cy cz ch cp ccx +//! ccz`: a gate the standard names is read as that gate, and any other as +//! standard gates, exactly (see [`Parser::phase_n`]). +//! +//! Refused, each with its line: classical control, the `inv` and `pow` +//! modifiers, a gate on a qubit after its measurement, and anything else not +//! listed. //! //! The standard gates: `id x y z h s sdg t tdg sx sxdg rx ry rz p phase u1 u2 //! u u3 U cx CX cnot cy cz ch cp cphase cu1 crx cry crz cu cu3 swap iswap rxx @@ -34,6 +40,9 @@ pub const MAX_QUBITS: u32 = 65_536; pub const MAX_OPS: usize = 2_000_000; /// How deep user gate definitions may nest. 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`. +const MAX_CONTROLS: u32 = 16; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Kind { @@ -146,6 +155,9 @@ fn normalise(kind: Kind, given: usize, args: Vec) -> Vec { } } +/// One op's Clifford gates and the qubits the op acts on. +pub type CliffordOp = (Vec, Vec); + /// One gate on named qubits. #[derive(Clone, Debug, PartialEq)] pub struct Op { @@ -498,7 +510,10 @@ impl Parser { let regs = if bits { &self.cregs } else { &self.qregs }; match regs.get(&name) { Some(r) => *r, - None => return self.err(format!("no {} register named {name}", if bits { "bit" } else { "qubit" })), + None => { + let (kind, decl) = if bits { ("bit", "bit") } else { ("qubit", "qubit") }; + return self.err(format!("no {kind} register named {name}; declare it first, e.g. `{decl}[2] {name};`")); + } } }; if self.eat("[") { @@ -592,8 +607,22 @@ impl Parser { self.expect(";")?; self.measure(&qs, &bs, line)?; } - "if" | "while" | "for" | "def" | "defcal" | "cal" | "box" | "let" | "const" | "input" | "output" | "ctrl" | "negctrl" | "inv" | "pow" | "delay" | "gphase" => { - return Err(format!("line {line}: `{word}` is not supported by this service")); + "ctrl" | "negctrl" => { + let polarity = self.modifiers(word == "ctrl")?; + let name = self.ident()?; + if matches!(name.as_str(), "inv" | "pow") { + return Err(format!("line {line}: `{name}` is not supported by this service; write the inverse or powered gate out, or define it with `gate`")); + } + self.call_with(name, line, &polarity)?; + } + "if" | "while" | "for" | "def" | "defcal" | "cal" | "box" | "let" | "const" | "input" | "output" | "inv" | "pow" | "delay" | "gphase" => { + let hint = match word.as_str() { + "if" | "while" => "; classical control is not simulated here: measure every qubit at the end instead", + "for" => "; unroll the loop into gates", + "inv" | "pow" => "; write the inverse or powered gate out, or define it with `gate`", + _ => "", + }; + return Err(format!("line {line}: `{word}` is not supported by this service{hint}")); } _ => { // `c = measure q;` / `c[0] = measure q[0];`, or a gate call. @@ -690,6 +719,41 @@ impl Parser { } fn call(&mut self, name: String, line: usize) -> Result<(), String> { + self.call_with(name, line, &[]) + } + + /// The controls a run of `ctrl` and `negctrl` modifiers adds, in order + /// (`true`: a control on |1⟩), the first modifier's word already read. + fn modifiers(&mut self, mut on: bool) -> Result, String> { + let mut polarity = Vec::new(); + loop { + let count = if self.eat("(") { + let n = self.integer()?; + self.expect(")")?; + n + } else { + 1 + }; + if count == 0 { + return self.err("a control modifier adds at least one control"); + } + if polarity.len() as u32 + count > MAX_CONTROLS { + return self.err(format!("modifiers add at most {MAX_CONTROLS} controls here")); + } + self.expect("@")?; + polarity.extend(std::iter::repeat_n(on, count as usize)); + match self.peek() { + Some(Tok::Ident(w)) if w == "ctrl" || w == "negctrl" => { + on = w == "ctrl"; + self.pos += 1; + } + _ => return Ok(polarity), + } + } + } + + /// A gate call, under the controls `polarity` adds (none for a plain call). + fn call_with(&mut self, name: String, line: usize, polarity: &[bool]) -> Result<(), String> { let mut params = Vec::new(); if self.eat("(") && !self.eat(")") { loop { @@ -716,7 +780,142 @@ impl Parser { } for k in 0..width { let qs: Vec = operands.iter().map(|o| if o.len() == 1 { o[0] } else { o[k] }).collect(); - self.apply(&name, ¶ms, &qs, line, 0)?; + if polarity.is_empty() { + self.apply(&name, ¶ms, &qs, line, 0)?; + } else { + self.controlled(&name, ¶ms, &qs, polarity, line)?; + } + } + Ok(()) + } + + /// A gate under control modifiers, its added controls the first qubits + /// named; a control on |0⟩ is a control between two `x`. The result is the + /// gate the standard names where there is one, and otherwise the base + /// gate's target turned into a phase: `x` = `h·z·h`, `y` = `s·x·sdg`, `h` = + /// `ry(π/4)·z·ry(−π/4)`, each exact. + fn controlled(&mut self, name: &str, params: &[f64], qubits: &[u32], polarity: &[bool], line: usize) -> Result<(), String> { + use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, PI}; + enum Base { + X, + Y, + H, + Phase(f64), + } + // (the base gate, the controls its name already has) + let (base, inner) = match (name, params) { + ("x", []) => (Base::X, 0), + ("y", []) => (Base::Y, 0), + ("h", []) => (Base::H, 0), + ("z", []) => (Base::Phase(PI), 0), + ("s", []) => (Base::Phase(FRAC_PI_2), 0), + ("sdg", []) => (Base::Phase(-FRAC_PI_2), 0), + ("t", []) => (Base::Phase(FRAC_PI_4), 0), + ("tdg", []) => (Base::Phase(-FRAC_PI_4), 0), + ("p" | "phase" | "u1", [t]) => (Base::Phase(*t), 0), + ("cx" | "CX" | "cnot", []) => (Base::X, 1), + ("cy", []) => (Base::Y, 1), + ("ch", []) => (Base::H, 1), + ("cz", []) => (Base::Phase(PI), 1), + ("cp" | "cphase" | "cu1", [t]) => (Base::Phase(*t), 1), + ("ccx" | "toffoli", []) => (Base::X, 2), + ("ccz", []) => (Base::Phase(PI), 2), + _ => { + return Err(format!( + "line {line}: a control modifier on `{name}` is not supported (under one: x y z h s sdg t tdg p, and cx cy cz ch cp ccx ccz); define the controlled gate with `gate`" + )); + } + }; + if let Base::Phase(t) = base + && !t.is_finite() + { + return Err(format!("line {line}: {name} has a parameter that is not finite")); + } + let want = polarity.len() + inner + 1; + if qubits.len() != want { + return Err(format!("line {line}: {name} under {} added control(s) takes {want} qubits, given {}", polarity.len(), qubits.len())); + } + for (i, q) in qubits.iter().enumerate() { + if qubits[..i].contains(q) { + return Err(format!("line {line}: qubit {q} named twice in one gate")); + } + } + let flip: Vec = qubits.iter().zip(polarity).filter(|(_, on)| !**on).map(|(q, _)| *q).collect(); + for &q in &flip { + self.apply("x", &[], &[q], line, 0)?; + } + let t = qubits[want - 1]; + match base { + Base::X => self.x_n(qubits, line)?, + Base::Y if qubits.len() <= 2 => self.apply(if qubits.len() == 1 { "y" } else { "cy" }, &[], qubits, line, 0)?, + Base::Y => { + self.apply("sdg", &[], &[t], line, 0)?; + self.x_n(qubits, line)?; + self.apply("s", &[], &[t], line, 0)?; + } + Base::H => match qubits.len() { + 1 => self.apply("h", &[], qubits, line, 0)?, + 2 => self.apply("ch", &[], qubits, line, 0)?, + _ => { + self.apply("ry", &[-FRAC_PI_4], &[t], line, 0)?; + self.phase_n(PI, qubits, line)?; + self.apply("ry", &[FRAC_PI_4], &[t], line, 0)?; + } + }, + Base::Phase(theta) => self.phase_n(theta, qubits, line)?, + } + for &q in &flip { + self.apply("x", &[], &[q], line, 0)?; + } + Ok(()) + } + + /// `x` on the last of `qs`, controlled by the others. + fn x_n(&mut self, qs: &[u32], line: usize) -> Result<(), String> { + let t = qs[qs.len() - 1]; + match qs.len() { + 1 => self.apply("x", &[], qs, line, 0), + 2 => self.apply("cx", &[], qs, line, 0), + 3 => self.apply("ccx", &[], qs, line, 0), + _ => { + self.apply("h", &[], &[t], line, 0)?; + self.phase_n(std::f64::consts::PI, qs, line)?; + self.apply("h", &[], &[t], line, 0) + } + } + } + + /// 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], line: usize) -> Result<(), String> { + let z = theta == std::f64::consts::PI; + match qs.len() { + 1 if z => return self.apply("z", &[], qs, line, 0), + 1 => return self.apply("p", &[theta], qs, line, 0), + 2 if z => return self.apply("cz", &[], qs, line, 0), + 2 => return self.apply("cp", &[theta], qs, line, 0), + 3 if z => return self.apply("ccz", &[], qs, line, 0), + _ => {} + } + let a = theta / (1u64 << (qs.len() - 1)) as f64; + for j in 0..qs.len() { + let lead = qs[j]; + self.apply("p", &[a], &[lead], line, 0)?; + 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.apply("cx", &[], &[qs[bit], lead], line, 0)?; + let size = (i ^ (i >> 1)).count_ones() + 1; + self.apply("p", &[if size % 2 == 1 { a } else { -a }], &[lead], line, 0)?; + } + // The code ends on the single bit j − 1: release it. + if j > 0 { + self.apply("cx", &[], &[qs[j - 1], lead], line, 0)?; + } } Ok(()) } @@ -1357,8 +1556,31 @@ impl Program { /// phase; refused when a gate is not Clifford at its angles (rotations /// must be whole quarter turns, controlled phases whole half turns). pub fn clifford(&self) -> Result, String> { + self.clifford_bounds().map(|(gates, _)| gates) + } + + /// The Clifford gates of each op, with the qubits the op acts on: what + /// noise after each gate attaches to, as the other noisy methods apply it. + pub fn clifford_ops(&self) -> Result, String> { + let (gates, ends) = self.clifford_bounds()?; + let mut start = 0; + Ok(self + .ops + .iter() + .zip(ends) + .map(|(op, end)| { + let g = gates[start..end].to_vec(); + start = end; + (g, op.qubits.clone()) + }) + .collect()) + } + + /// The Clifford lowering, and where each op's gates end in it. + fn clifford_bounds(&self) -> Result<(Vec, Vec), String> { use Kind::*; let mut out = Vec::new(); + let mut ends = Vec::with_capacity(self.ops.len()); for op in &self.ops { let q = &op.qubits; let p = &op.params; @@ -1445,8 +1667,9 @@ impl Program { out.extend(post); } } + ends.push(out.len()); } - Ok(out) + Ok((out, ends)) } /// The classical outcome a measurement of the qubits gives, as the bit @@ -1503,6 +1726,13 @@ mod tests { for (src, needle) in [ ("qubit[1] q;\nbit c;\nc = measure q;\nx q;", "line 4"), ("qubit[1] q;\nif (c) x q;", "not supported"), + ("qubit[1] q;\nif (c) x q;", "measure every qubit at the end"), + ("qubit[1] q;\nfor uint i in [0:3] { x q; }", "unroll the loop"), + ("qubit[1] q;\ninv @ x q[0];", "write the inverse"), + ("qubit[2] q;\nctrl @ rz(0.1) q[0], q[1];", "a control modifier on `rz` is not supported"), + ("qubit[3] q;\nctrl @ x q[0], q[1], q[2];", "takes 2 qubits"), + ("qubit[2] q;\nctrl(0) @ x q[0], q[1];", "at least one control"), + ("qubit[18] q;\nctrl(17) @ x q;", "at most 16 controls"), ("qubit[1] q;\nfoo q;", "unknown gate foo"), ("qubit[1] q;\nrx q;", "rx takes 1 parameters"), ("qubit[2] q;\ncx q[0], q[0];", "named twice"), @@ -1562,6 +1792,72 @@ mod tests { sv } + #[test] + fn a_controlled_gate_is_its_gate_where_the_controls_hold_and_nothing_elsewhere() { + use std::f64::consts::FRAC_1_SQRT_2 as R; + // A state with no special structure on 6 qubits; then each modified + // gate, against the 2×2 gate applied to the amplitudes directly where + // the controls hold. Exactly, phase and all: a controlled gate exposes + // any global phase its expansion has. + let prep = "qubit[6] q;\nry(0.3) q[0]; rx(1.1) q[1]; ry(2.2) q[2]; rz(0.7) q[2]; rx(0.4) q[3]; ry(1.7) q[4]; ry(0.6) q[5]; cx q[0], q[3]; rz(0.9) q[4]; crx(0.8) q[5], q[1];\n"; + let (c4, s4) = (std::f64::consts::FRAC_PI_4.cos(), std::f64::consts::FRAC_PI_4.sin()); + let (cp, sp) = (0.37f64.cos(), 0.37f64.sin()); + let gates: [(&str, [(f64, f64); 4]); 9] = [ + ("x", [(0.0, 0.0), (1.0, 0.0), (1.0, 0.0), (0.0, 0.0)]), + ("y", [(0.0, 0.0), (0.0, -1.0), (0.0, 1.0), (0.0, 0.0)]), + ("z", [(1.0, 0.0), (0.0, 0.0), (0.0, 0.0), (-1.0, 0.0)]), + ("h", [(R, 0.0), (R, 0.0), (R, 0.0), (-R, 0.0)]), + ("s", [(1.0, 0.0), (0.0, 0.0), (0.0, 0.0), (0.0, 1.0)]), + ("sdg", [(1.0, 0.0), (0.0, 0.0), (0.0, 0.0), (0.0, -1.0)]), + ("t", [(1.0, 0.0), (0.0, 0.0), (0.0, 0.0), (c4, s4)]), + ("tdg", [(1.0, 0.0), (0.0, 0.0), (0.0, 0.0), (c4, -s4)]), + ("p(0.37)", [(1.0, 0.0), (0.0, 0.0), (0.0, 0.0), (cp, sp)]), + ]; + let base = dense(&parse(prep).unwrap()); + let n = 6u32; + let bit = |q: u32| 1usize << (n - 1 - q); + let order = [3u32, 0, 5, 4, 1]; + for (g, u) in gates { + for polarity in [vec![true], vec![false], vec![true, true], vec![false, true], vec![true, true, true], vec![true, false, true, false], vec![true; 5]] { + let mods: String = polarity.iter().map(|&on| if on { "ctrl @ " } else { "negctrl @ " }).collect(); + let controls = &order[..polarity.len()]; + let target = 2u32; + let operands: Vec = controls.iter().chain([&target]).map(|q| format!("q[{q}]")).collect(); + let got = dense(&parse(&format!("{prep}{mods}{g} {};", operands.join(", "))).unwrap()); + let (mut re, mut im) = (base.re.clone(), base.im.clone()); + for i0 in 0..1usize << n { + if i0 & bit(target) != 0 || !controls.iter().zip(&polarity).all(|(&c, &on)| (i0 & bit(c) != 0) == on) { + continue; + } + let i1 = i0 | bit(target); + let (a0, a1) = ((re[i0], im[i0]), (re[i1], im[i1])); + let mul = |m: (f64, f64), a: (f64, f64)| (m.0 * a.0 - m.1 * a.1, m.0 * a.1 + m.1 * a.0); + let (x0, x1) = (mul(u[0], a0), mul(u[1], a1)); + let (y0, y1) = (mul(u[2], a0), mul(u[3], a1)); + (re[i0], im[i0]) = (x0.0 + x1.0, x0.1 + x1.1); + (re[i1], im[i1]) = (y0.0 + y1.0, y0.1 + y1.1); + } + for i in 0..1usize << n { + assert!((got.re[i] - re[i]).abs() < 1e-12 && (got.im[i] - im[i]).abs() < 1e-12, "{mods}{g}: amplitude {i}"); + } + } + } + // A controlled name under a modifier is the gate with both controls. + for (a, b) in [("ctrl @ cx q[3], q[0], q[2];", "ctrl(2) @ x q[3], q[0], q[2];"), ("negctrl @ cp(0.37) q[4], q[1], q[2];", "negctrl @ ctrl @ p(0.37) q[4], q[1], q[2];"), ("ctrl(2) @ ccz q[0], q[1], q[3], q[4], q[2];", "ctrl(4) @ z q[0], q[1], q[3], q[4], q[2];")] { + let (x, y) = (dense(&parse(&format!("{prep}{a}")).unwrap()), dense(&parse(&format!("{prep}{b}")).unwrap())); + assert!(x.re.iter().zip(&y.re).chain(x.im.iter().zip(&y.im)).all(|(p, q)| (p - q).abs() < 1e-12), "{a} / {b}"); + } + } + + #[test] + fn a_controlled_gate_the_standard_names_is_read_as_it() { + let p = parse("qubit[3] q;\nctrl @ x q[0], q[1];\nctrl(2) @ z q[0], q[1], q[2];\nnegctrl @ z q[0], q[1];\nctrl @ ctrl @ x q[2], q[1], q[0];\nctrl @ h q[1], q[0];\nctrl @ y q[1], q[2];").unwrap(); + let kinds: Vec = p.ops.iter().map(|o| o.kind).collect(); + assert_eq!(kinds, vec![Kind::Cx, Kind::Ccz, Kind::X, Kind::Cz, Kind::X, Kind::Ccx, Kind::Ch, Kind::Cy]); + // So a Clifford program under modifiers keeps the stabilizer method. + assert!(parse("qubit[2] q;\nctrl @ x q[0], q[1];\nnegctrl @ z q[0], q[1];\nctrl @ y q[1], q[0];").unwrap().clifford().is_ok()); + } + #[test] fn the_matrices_are_unitary() { for op in every_kind(4, 3).ops { diff --git a/wai/wai-quantum-service/src/receipt.rs b/wai/wai-quantum-service/src/receipt.rs index a10f7e5be..c6a36518c 100644 --- a/wai/wai-quantum-service/src/receipt.rs +++ b/wai/wai-quantum-service/src/receipt.rs @@ -1,12 +1,24 @@ //! Signed receipts: every finished job's result is sealed with the service's //! Ed25519 key, binding the request as submitted and the result as returned, //! so a client can show anyone what was run and what came back. +//! +//! What is signed is the JSON Canonicalization Scheme (RFC 8785) text of each: +//! keys sorted, no whitespace, and every number written as ECMAScript writes +//! the double it reads. A result therefore verifies after any JSON reader and +//! writer has carried it: a browser's `JSON.parse` and `JSON.stringify` turn +//! `1.0` into `1`, and other readers reorder keys. use serde_json::{Value, json}; use std::path::{Path, PathBuf}; use wai_quantum::ed25519_dalek::{Signer as _, SigningKey, Verifier as _, VerifyingKey}; -const DOMAIN: &[u8] = b"wai:quantum-service-receipt\x01"; +/// The first receipts signed serde_json's own text, which only a reader that +/// keeps each number's spelling and each object's key order can reproduce. +/// They still verify; new ones are canonical. +const DOMAIN_V1: &[u8] = b"wai:quantum-service-receipt\x01"; +const DOMAIN: &[u8] = b"wai:quantum-service-receipt\x02"; +/// The receipt's name for its canonical form. +const CANON: &str = "rfc8785"; /// The service's signing key. pub struct Signer { @@ -29,15 +41,229 @@ fn put(o: &mut Vec, bytes: &[u8]) { } /// The digest a receipt signs: BLAKE3(domain ‖ put(request) ‖ put(result)), -/// each the compact JSON text serde_json writes (keys in insertion order, as -/// the service builds them and returns them). +/// each in its RFC 8785 canonical text. pub fn digest(request: &Value, result: &Value) -> [u8; 32] { let mut o = DOMAIN.to_vec(); + put(&mut o, &canonical(request)); + put(&mut o, &canonical(result)); + *blake3::hash(&o).as_bytes() +} + +/// The digest the first receipts signed (see [`DOMAIN_V1`]). +fn digest_v1(request: &Value, result: &Value) -> [u8; 32] { + let mut o = DOMAIN_V1.to_vec(); put(&mut o, request.to_string().as_bytes()); put(&mut o, result.to_string().as_bytes()); *blake3::hash(&o).as_bytes() } +/// The RFC 8785 canonical text of `v`. +pub fn canonical(v: &Value) -> Vec { + let mut o = Vec::new(); + write(v, &mut o); + o +} + +fn write(v: &Value, o: &mut Vec) { + match v { + Value::Null => o.extend_from_slice(b"null"), + Value::Bool(b) => o.extend_from_slice(if *b { b"true" } else { b"false" }), + // Every number as the double a JSON reader in ECMAScript holds. + Value::Number(n) => o.extend_from_slice(es_number(n.as_f64().unwrap_or(f64::NAN)).as_bytes()), + Value::String(s) => write_str(s, o), + Value::Array(a) => { + o.push(b'['); + for (i, x) in a.iter().enumerate() { + if i > 0 { + o.push(b','); + } + write(x, o); + } + o.push(b']'); + } + Value::Object(m) => { + // Keys in the order of their UTF-16 code units. + let mut keys: Vec<&String> = m.keys().collect(); + keys.sort_by(|a, b| a.encode_utf16().cmp(b.encode_utf16())); + o.push(b'{'); + for (i, k) in keys.into_iter().enumerate() { + if i > 0 { + o.push(b','); + } + write_str(k, o); + o.push(b':'); + write(&m[k], o); + } + o.push(b'}'); + } + } +} + +/// A string as RFC 8785 writes it: `"` and `\\` escaped, the five short +/// control escapes, every other control character as `\u00xx`, and the rest +/// as it is. +fn write_str(s: &str, o: &mut Vec) { + o.push(b'"'); + for c in s.chars() { + match c { + '"' => o.extend_from_slice(b"\\\""), + '\\' => o.extend_from_slice(b"\\\\"), + '\u{8}' => o.extend_from_slice(b"\\b"), + '\t' => o.extend_from_slice(b"\\t"), + '\n' => o.extend_from_slice(b"\\n"), + '\u{c}' => o.extend_from_slice(b"\\f"), + '\r' => o.extend_from_slice(b"\\r"), + c if (c as u32) < 0x20 => o.extend_from_slice(format!("\\u{:04x}", c as u32).as_bytes()), + c => { + let mut b = [0u8; 4]; + o.extend_from_slice(c.encode_utf8(&mut b).as_bytes()); + } + } + } + o.push(b'"'); +} + +/// A double as ECMAScript's Number::toString writes it (ECMA-262, 6.1.6.1.20): +/// the shortest digits that read back as `x`, laid out by where the decimal +/// point falls. JSON holds no infinity or NaN; they write as `null`. +pub fn es_number(x: f64) -> String { + if !x.is_finite() { + return "null".into(); + } + if x == 0.0 { + return "0".into(); + } + let (s, n) = shortest(x.abs()); + let digits: String = s.iter().map(|d| char::from(b'0' + d)).collect(); + let k = s.len() as i32; + let mut out = String::new(); + if x < 0.0 { + out.push('-'); + } + if k <= n && n <= 21 { + out.push_str(&digits); + out.extend(std::iter::repeat_n('0', (n - k) as usize)); + } else if 0 < n && n <= 21 { + out.push_str(&digits[..n as usize]); + out.push('.'); + out.push_str(&digits[n as usize..]); + } else if -6 < n && n <= 0 { + out.push_str("0."); + out.extend(std::iter::repeat_n('0', (-n) as usize)); + out.push_str(&digits); + } else { + out.push_str(&digits[..1]); + if k > 1 { + out.push('.'); + out.push_str(&digits[1..]); + } + out.push('e'); + out.push(if n - 1 < 0 { '-' } else { '+' }); + out.push_str(&(n - 1).abs().to_string()); + } + out +} + +/// The digits s and the exponent n, x = 0.s × 10^n, that ECMAScript chooses: +/// the fewest that read back as `x` (> 0), and of those the nearest to `x`, +/// a tie going to the even. +fn shortest(x: f64) -> (Vec, i32) { + let e = format!("{x:e}"); + let (mant, exp) = e.split_once('e').unwrap_or((&e, "0")); + let mut s: Vec = mant.bytes().filter(u8::is_ascii_digit).map(|b| b - b'0').collect(); + while s.len() > 1 && s.last() == Some(&0) { + s.pop(); + } + let n = exp.parse::().unwrap_or(0) + 1; + // Rust's digits are the fewest, but a tie between two equally near it + // rounds up. Only when another of the same length also reads back as x + // (near 17 digits) is there a choice: then take x's exact expansion (a + // double's has at most 767 significant digits) rounded to that length. + if ![false, true].into_iter().any(|up| step(&s, up).is_some_and(|t| reads_as(&t, n, x))) { + return (s, n); + } + let k = s.len(); + let exact = format!("{x:.800e}"); + let (em, ee) = exact.split_once('e').unwrap_or((&exact, "0")); + let all: Vec = em.bytes().filter(u8::is_ascii_digit).map(|b| b - b'0').collect(); + let mut cn = ee.parse::().unwrap_or(0) + 1; + let mut c = all[..k].to_vec(); + let rest = &all[k..]; + let up = match rest.first().copied().unwrap_or(0) { + d if d > 5 => true, + 5 => rest[1..].iter().any(|&d| d != 0) || c[k - 1] % 2 == 1, + _ => false, + }; + if up { + match step(&c, true) { + Some(t) => c = t, + None => { + // 99…9 rounds to 10…0. + c = std::iter::once(1).chain(std::iter::repeat_n(0, k - 1)).collect(); + cn += 1; + } + } + } + if reads_as(&c, cn, x) { (c, cn) } else { (s, n) } +} + +/// The k-digit decimal one unit above or below `s` in its last digit, if it +/// still has k digits. +fn step(s: &[u8], up: bool) -> Option> { + let mut t = s.to_vec(); + for i in (0..t.len()).rev() { + match (up, t[i]) { + (true, 9) => t[i] = 0, + (false, 0) => t[i] = 9, + (true, d) | (false, d) => { + t[i] = if up { d + 1 } else { d - 1 }; + return (t[0] != 0).then_some(t); + } + } + } + None +} + +/// Whether 0.s × 10^n reads as `x` (std's reader is correctly rounded). +fn reads_as(s: &[u8], n: i32, x: f64) -> bool { + let digits: String = s.iter().map(|d| char::from(b'0' + d)).collect(); + format!("0.{digits}e{n}").parse::().is_ok_and(|y| y == x) +} + +/// The first number in `v` that a double cannot hold exactly (an integer past +/// ±2^53), by its path. Such a number would be read differently by readers that +/// hold every number as a double, so the receipt could not bind it. +pub fn inexact_number(v: &Value) -> Option { + const SAFE: u64 = (1 << 53) - 1; + fn walk(v: &Value, path: &mut String) -> bool { + match v { + Value::Number(n) => n.as_u64().is_some_and(|u| u > SAFE) || n.as_i64().is_some_and(|i| i.unsigned_abs() > SAFE), + Value::Array(a) => a.iter().enumerate().any(|(i, x)| { + let at = path.len(); + path.push_str(&format!("[{i}]")); + let hit = walk(x, path); + if !hit { + path.truncate(at); + } + hit + }), + Value::Object(m) => m.iter().any(|(k, x)| { + let at = path.len(); + path.push('.'); + path.push_str(k); + let hit = walk(x, path); + if !hit { + path.truncate(at); + } + hit + }), + _ => false, + } + } + let mut path = String::new(); + walk(v, &mut path).then(|| path.trim_start_matches('.').to_string()) +} + impl Signer { /// The key at `path`, created (32 random bytes, readable by the owner /// only) when there is none. @@ -70,7 +296,7 @@ impl Signer { pub fn seal(&self, request: &Value, result: &Value) -> Value { let d = digest(request, result); let sig = self.key.sign(&d); - json!({ "alg": "ed25519", "key": self.public_key(), "digest": hex(&d), "signature": hex(&sig.to_bytes()) }) + json!({ "alg": "ed25519", "canon": CANON, "key": self.public_key(), "digest": hex(&d), "signature": hex(&sig.to_bytes()) }) } } @@ -81,7 +307,11 @@ pub fn verify(request: &Value, result: &Value, receipt: &Value) -> bool { let (Some(key), Some(sig)) = (field("key"), field("signature")) else { return false }; let (Ok(key), Ok(sig)) = (<[u8; 32]>::try_from(key.as_slice()), <[u8; 64]>::try_from(sig.as_slice())) else { return false }; let Ok(vk) = VerifyingKey::from_bytes(&key) else { return false }; - let d = digest(request, result); + let d = match receipt.get("canon").and_then(Value::as_str) { + Some(CANON) => digest(request, result), + None => digest_v1(request, result), + Some(_) => return false, + }; field("digest").is_some_and(|g| g == d) && vk.verify(&d, &wai_quantum::ed25519_dalek::Signature::from_bytes(&sig)).is_ok() } @@ -125,6 +355,86 @@ mod tests { assert!(!verify(&req, &res, &forged)); } + #[test] + fn numbers_are_written_as_ecmascript_writes_them() { + // Bit patterns and ECMAScript's Number::toString of each, written by a + // JavaScript engine: RFC 8785's appendix B, the edges of each layout, + // and patterns spread over every exponent. + let mut n = 0; + for line in include_str!("../testdata/es-number-tostring.txt").lines() { + let (bits, want) = line.split_once(' ').unwrap(); + let x = f64::from_bits(u64::from_str_radix(bits, 16).unwrap()); + assert_eq!(es_number(x), want, "{bits}"); + // And the text reads back as the same double, as it must for a + // result to verify after a round trip (serde_json's + // float_roundtrip: its default reader rounds twice). + if x != 0.0 { + assert_eq!(serde_json::from_str::(want).unwrap().to_bits(), x.to_bits(), "{bits} {want}"); + } + n += 1; + } + assert!(n > 3000); + } + + /// The same against a larger set, in the same format, written by a + /// JavaScript engine: `WAI_ES_VECTORS=path cargo test -- --ignored`. + #[test] + #[ignore] + fn numbers_match_a_javascript_engine_on_a_large_set() { + let path = std::env::var("WAI_ES_VECTORS").expect("WAI_ES_VECTORS: a file of `hexbits text` lines"); + let text = std::fs::read_to_string(path).unwrap(); + let mut n = 0u64; + for line in text.lines() { + let (bits, want) = line.split_once(' ').unwrap(); + assert_eq!(es_number(f64::from_bits(u64::from_str_radix(bits, 16).unwrap())), want, "{bits}"); + n += 1; + } + eprintln!("{n} numbers agree"); + } + + #[test] + fn the_canonical_text_sorts_keys_and_writes_numbers_and_strings_one_way() { + let v = json!({ "b": 1.0, "a": [true, null, "x\n\u{1}\"\\é"], "\u{e000}": 0.000001, "\u{1f600}": -0.0, "c": 1e21 }); + // U+1F600 is a surrogate pair, so it sorts before U+E000. + assert_eq!(String::from_utf8(canonical(&v)).unwrap(), "{\"a\":[true,null,\"x\\n\\u0001\\\"\\\\é\"],\"b\":1,\"c\":1e+21,\"\u{1f600}\":0,\"\u{e000}\":0.000001}"); + } + + #[test] + fn a_receipt_verifies_after_a_round_trip_through_another_json_writer() { + let s = Signer::from_seed([7; 32]); + let req = json!({ "qasm": "qubit[2] q; h q[0]; cx q[0], q[1];", "shots": 1000, "observables": ["Z0 Z1"] }); + let res = json!({ "method": "stabilizer", "expectations": [{ "observable": "Z0 Z1", "coeff": 1.0, "value": 1.0 }], "threshold": 0.00001, "elapsed_ms": 0 }); + let r = s.seal(&req, &res); + // What a browser sends back: integral doubles without a fraction, keys + // in another order, the threshold in plain decimals. + let req2: Value = serde_json::from_str(r#"{"observables":["Z0 Z1"],"shots":1000,"qasm":"qubit[2] q; h q[0]; cx q[0], q[1];"}"#).unwrap(); + let res2: Value = serde_json::from_str(r#"{"elapsed_ms":0,"threshold":0.00001,"method":"stabilizer","expectations":[{"value":1,"coeff":1,"observable":"Z0 Z1"}]}"#).unwrap(); + assert!(verify(&req2, &res2, &r)); + let res3: Value = serde_json::from_str(r#"{"elapsed_ms":0,"threshold":0.00001,"method":"stabilizer","expectations":[{"value":0.999,"coeff":1,"observable":"Z0 Z1"}]}"#).unwrap(); + assert!(!verify(&req2, &res3, &r)); + } + + #[test] + fn the_first_receipts_still_verify_and_an_unknown_form_does_not() { + let s = Signer::from_seed([7; 32]); + let req = json!({ "qasm": "OPENQASM 3.0;", "shots": 10 }); + let res = json!({ "method": "statevector", "counts": { "0": 10 } }); + let d = digest_v1(&req, &res); + let old = json!({ "alg": "ed25519", "key": s.public_key(), "digest": hex(&d), "signature": hex(&s.key.sign(&d).to_bytes()) }); + assert!(verify(&req, &res, &old)); + let mut other = s.seal(&req, &res); + other["canon"] = json!("something-else"); + assert!(!verify(&req, &res, &other)); + } + + #[test] + fn integers_a_double_cannot_hold_are_found_by_path() { + assert_eq!(inexact_number(&json!({ "seed": 9007199254740991u64, "shots": 10 })), None); + assert_eq!(inexact_number(&json!({ "seed": 9007199254740992u64 })).as_deref(), Some("seed")); + assert_eq!(inexact_number(&json!({ "options": { "x": [1, -9007199254740993i64] } })).as_deref(), Some("options.x[1]")); + assert_eq!(inexact_number(&json!({ "noise": { "readout": 1e300 } })), None); + } + #[test] fn a_key_is_created_once_private_and_then_reused() { let dir = std::env::temp_dir().join(format!("wai-qsvc-key-{}", std::process::id())); diff --git a/wai/wai-quantum-service/src/run.rs b/wai/wai-quantum-service/src/run.rs index d6a59de41..02150748f 100644 --- a/wai/wai-quantum-service/src/run.rs +++ b/wai/wai-quantum-service/src/run.rs @@ -127,6 +127,36 @@ pub fn run(request: &Value, plan: &Plan) -> Result { ); } } + "statevector_single" => { + use wai_quantum::quantum_sv_metal::GpuState; + let gates = r.program.matrices(); + // Wider fused gates run slower on the GPU than the passes they save. + let width = r.options.get("fusion").and_then(Value::as_u64).unwrap_or(3).clamp(1, 3) as usize; + let mut sv = GpuState::zero(n).map_err(|e| e.to_string())?; + sv.run(&gates, width).map_err(|e| e.to_string())?; + out["accuracy"] = json!({ "class": "single", "fusion": width, "device": sv.device(), "note": "single-precision amplitudes on the GPU, about 1e-7 relative rounding per gate; sums in double precision" }); + if r.shots > 0 { + let mut map: HashMap = HashMap::new(); + for x in sv.sample(r.shots as usize, r.seed) { + *map.entry(r.program.bitstring(|q| x >> (n - 1 - q) & 1 == 1)).or_insert(0) += 1; + } + merge(&mut out, counts(map)); + } + if !r.observables.is_empty() { + out["expectations"] = expectation_entries(&r, |s| sv.expectation(&[(1.0, s.to_vec())])); + } + if !r.amplitudes.is_empty() { + out["amplitudes"] = Value::Array( + r.amplitudes + .iter() + .map(|b| { + let a = sv.amplitude(index_of(b, n, |q| n - 1 - q)); + json!({ "bits": b, "re": a.re, "im": a.im }) + }) + .collect(), + ); + } + } "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; @@ -155,6 +185,60 @@ pub fn run(request: &Value, plan: &Plan) -> Result { ); } } + "stabilizer" if r.noise.is_some() => { + use wai_quantum::quantum_tableau::{FrameStep, frame_counts, frame_expectations}; + let noise = r.noise.ok_or("no noise model")?; + // Each op's gates, then its noise on the qubits it touched, as the + // other noisy methods apply it. + let mut steps = Vec::new(); + for (gates, qs) in r.program.clifford_ops()? { + steps.extend(gates.into_iter().map(FrameStep::Gate)); + if qs.len() == 2 && noise.depol2 > 0.0 { + steps.push(FrameStep::Depolarize2(qs[0], qs[1], noise.depol2)); + } + for &q in &qs { + if qs.len() != 2 && noise.depol1 > 0.0 { + steps.push(FrameStep::Depolarize1(q, noise.depol1)); + } + if noise.dephasing > 0.0 { + steps.push(FrameStep::Dephase(q, noise.dephasing)); + } + } + } + let threads = std::thread::available_parallelism().map_or(1, |t| t.get()).min(8); + let frame_shots = catalog::frame_expectation_shots(&r); + out["accuracy"] = json!({ + "class": "estimate", + "shots": frame_shots, + "note": "Pauli frames against a noiseless stabilizer-tableau reference: counts are samples with the law of independent noisy shots; expectations are the noiseless values times the mean sign of the frames, with standard errors" + }); + if r.shots > 0 { + let measured: Vec = r.program.measured.iter().map(|&(q, _)| q).collect(); + let nb = r.program.n_bits as usize; + let mut map: HashMap = HashMap::new(); + for (bits, k) in frame_counts(n, &steps, &measured, noise.readout, r.shots, r.seed, threads).map_err(|e| e.to_string())? { + let mut s = vec![b'0'; nb]; + for (j, &(_, b)) in r.program.measured.iter().enumerate() { + if bits[j / 64] >> (j % 64) & 1 == 1 { + s[nb - 1 - b as usize] = b'1'; + } + } + *map.entry(String::from_utf8(s).expect("ASCII")).or_insert(0) += k; + } + merge(&mut out, counts(map)); + } + if !r.observables.is_empty() { + let strings: Vec> = r.observables.iter().map(|(_, _, s)| s.clone()).collect(); + let est = frame_expectations(n, &steps, &strings, frame_shots, r.seed, threads).map_err(|e| e.to_string())?; + out["expectations"] = Value::Array( + r.observables + .iter() + .zip(est) + .map(|((text, coeff, _), (v, se))| json!({ "observable": text, "coeff": coeff, "value": coeff * v, "stderr": coeff.abs() * se })) + .collect(), + ); + } + } "stabilizer" => { use wai_quantum::quantum_tableau::Tableau; let t = Tableau::run(n, &r.program.clifford()?).map_err(|e| e.to_string())?; @@ -187,8 +271,16 @@ pub fn run(request: &Value, plan: &Plan) -> Result { } } "pauli" => { - use wai_quantum::quantum_spd::{Pauli, SpdConfig, expectation}; + use wai_quantum::quantum_spd::{Pauli, SpdConfig, expectation_capped}; let rc = r.program.rotations(); + // As many terms as the job's memory holds: a term's string (two + // words of bits a 64 qubits, padded to a power of two), its + // coefficient and index slots, and room for a rotation's new terms. + let words = (n as u64).div_ceil(64).next_power_of_two(); + let per_term = 2 * (16 * words + 8) + 16; + let fits = ((plan.memory_bytes.saturating_sub(256 << 20)) / per_term).max(1 << 16) as usize; + let max_terms = r.options.get("max_terms").and_then(Value::as_u64).map_or(fits, |m| (m as usize).clamp(1, fits)); + let threads = std::thread::available_parallelism().map_or(1, |t| t.get()); let threshold = r.options.get("threshold").and_then(Value::as_f64).unwrap_or(1e-5).clamp(0.0, 1.0); let max_weight = r.options.get("max_weight").and_then(Value::as_u64).map(|w| w as u32); // The ladder: the same value at up to three thresholds a decade @@ -202,12 +294,22 @@ pub fn run(request: &Value, plan: &Plan) -> Result { let mut steps = Vec::new(); let mut last = None; for &t in &rungs { - let res = expectation(&rc, &[(*coeff, obs.clone())], &[], &SpdConfig { threshold: t, max_weight }).map_err(|e| e.to_string())?; - steps.push(json!({ "threshold": t, "value": res.value, "bound": res.truncation_bound, "peak_terms": res.peak_terms })); - last = Some(res); + let (res, used) = expectation_capped(&rc, &[(*coeff, obs.clone())], &[], &SpdConfig { threshold: t, max_weight }, threads, max_terms).map_err(|e| e.to_string())?; + let mut step = json!({ "threshold": t, "value": res.value, "bound": res.truncation_bound, "peak_terms": res.peak_terms }); + if used > t { + step["threshold_reached"] = json!(used); + } + steps.push(step); + last = Some((res, used)); } - let res = last.expect("one rung at least"); + let (res, used) = last.expect("one rung at least"); let mut entry = json!({ "observable": text, "coeff": coeff, "value": res.value, "bound": res.truncation_bound, "peak_terms": res.peak_terms }); + if used > threshold { + // The sum outgrew the job's memory: the threshold rose to + // fit, and the bound covers what that dropped. + entry["threshold_reached"] = json!(used); + entry["max_terms"] = json!(max_terms); + } if steps.len() > 1 { entry["ladder"] = Value::Array(steps); } @@ -277,7 +379,7 @@ pub fn run(request: &Value, plan: &Plan) -> Result { "density_matrix" => { use wai_quantum::quantum_noisy::DensityMatrix; let noise = r.noise.ok_or("no noise model")?; - let rho = DensityMatrix::run(&r.program.matrices(), n, &noise).map_err(|e| format!("{e:?}"))?; + let rho = DensityMatrix::evolve(&r.program.matrices(), n, &noise).map_err(|e| format!("{e:?}"))?; out["accuracy"] = json!({ "class": "exact", "note": "the noisy state itself, under the model; counts are seeded samples of its distribution" }); if r.shots > 0 { let p = rho.probabilities(noise.readout); @@ -378,6 +480,22 @@ mod tests { run(&job, &p).unwrap() } + #[test] + fn grover_as_the_ide_exports_it_finds_the_marked_item() { + // Its oracle and its diffusion are each a Z under four controls, + // written `ctrl(4) @ z`. Four rounds on five qubits find the marked + // item with probability 0.9992. + let marked = 0b10110u32; + let qasm = wai_quantum::quantum_toolchain::to_qasm(&wai_quantum::quantum_toolchain::grover(5, marked)); + assert!(qasm.contains("ctrl(4) @ z"), "{qasm}"); + let key: String = (0..5).rev().map(|q| if marked >> q & 1 == 1 { '1' } else { '0' }).collect(); + for method in ["exact", "statevector"] { + let r = go(json!({ "qasm": qasm, "method": method, "shots": 1000, "seed": 3 })); + let hits = r["counts"][&key].as_u64().unwrap_or(0); + assert!(hits > 980, "{method}: {r}"); + } + } + #[test] fn a_bell_pair_is_the_same_on_every_method_that_takes_it() { let qasm = "OPENQASM 3.0; qubit[2] q; bit[2] c; h q[0]; cx q[0], q[1]; c = measure q;"; @@ -455,6 +573,80 @@ mod tests { assert!(histories < shots / 4, "{histories} histories for {shots} shots"); } + #[test] + fn noisy_clifford_circuits_run_on_frames_and_agree_with_the_density_matrix() { + let qasm = "qubit[4] q; bit[4] c; h q[0]; cx q[0], q[1]; s q[1]; cx q[1], q[2]; h q[3]; cz q[3], q[2]; rx(pi/2) q[0]; c = measure q;"; + let noise = json!({ "depolarizing_1q": 0.01, "depolarizing_2q": 0.03, "dephasing": 0.01, "readout": 0.02 }); + let p = plan(&json!({ "qasm": qasm, "noise": noise, "shots": 100 }), CAP).unwrap(); + // Small enough for the exact density matrix, which auto prefers. + assert_eq!(p.method, "density_matrix"); + let shots = 100_000u64; + let dm = go(json!({ "qasm": qasm, "method": "density_matrix", "noise": noise, "shots": 1_000_000, "observables": ["Z0 Z1", "X3 Z2", "Y1"] })); + let st = go(json!({ "qasm": qasm, "method": "stabilizer", "noise": noise, "shots": shots, "seed": 5, "observables": ["Z0 Z1", "X3 Z2", "Y1"] })); + assert_eq!(st["method"], "stabilizer", "{st}"); + assert_eq!(st["accuracy"]["class"], "estimate"); + for (bits, c) in st["counts"].as_object().unwrap() { + let p = dm["counts"][bits].as_u64().unwrap_or(0) as f64 / 1e6; + let f = c.as_u64().unwrap() as f64 / shots as f64; + let se = (p * (1.0 - p) / shots as f64).sqrt().max(1e-4); + assert!((f - p).abs() < 5.0 * se + 2e-3, "{bits}: {f} vs {p}"); + } + for k in 0..3 { + let (v, se) = (st["expectations"][k]["value"].as_f64().unwrap(), st["expectations"][k]["stderr"].as_f64().unwrap()); + let want = dm["expectations"][k]["value"].as_f64().unwrap(); + assert!((v - want).abs() < 5.0 * se + 1e-9, "observable {k}: {v} ± {se} vs {want}"); + } + // Wide: auto takes the frames for a noisy Clifford circuit past the density matrix. + let wide = format!("qubit[300] q; h q[0]; {} ", (0..299).map(|i| format!("cx q[{i}], q[{}];", i + 1)).collect::()); + let w = go(json!({ "qasm": wide, "noise": { "depolarizing_2q": 0.001 }, "shots": 1000, "observables": ["Z0 Z299"] })); + assert_eq!(w["method"], "stabilizer", "{w}"); + let v = w["expectations"][0]["value"].as_f64().unwrap(); + assert!(v > 0.5 && v < 1.0, "{v}"); + // Amplitude damping is not a Pauli channel. + let e = plan(&json!({ "qasm": wide, "method": "stabilizer", "noise": { "amplitude_damping": 0.01 } }), CAP).unwrap_err(); + assert!(e.contains("amplitude damping"), "{e}"); + } + + #[test] + fn the_gpu_state_vector_agrees_where_there_is_one_and_is_refused_where_not() { + let qasm = "qubit[14] q; bit[14] c; h q; rx(0.3) q[0]; cx q[0], q[13]; crz(0.7) q[3], q[9]; ry(1.1) q[5]; c = measure q;"; + let job = json!({ "qasm": qasm, "method": "statevector_single", "shots": 2000, "observables": ["Z0 Z13", "X5"], "amplitudes": ["00000000000000"] }); + match crate::catalog::gpu_max_qubits() { + Ok(_) => { + let g = go(job); + let d = go(json!({ "qasm": qasm, "method": "statevector", "observables": ["Z0 Z13", "X5"], "amplitudes": ["00000000000000"] })); + assert_eq!(g["accuracy"]["class"], "single"); + for k in 0..2 { + let (a, b) = (g["expectations"][k]["value"].as_f64().unwrap(), d["expectations"][k]["value"].as_f64().unwrap()); + assert!((a - b).abs() < 1e-5, "{a} vs {b}"); + } + let (a, b) = (g["amplitudes"][0]["re"].as_f64().unwrap(), d["amplitudes"][0]["re"].as_f64().unwrap()); + assert!((a - b).abs() < 1e-6); + assert_eq!(g["counts"].as_object().unwrap().values().map(|v| v.as_u64().unwrap()).sum::(), 2000); + } + Err(_) => { + let e = plan(&job, CAP).unwrap_err(); + assert!(e.contains("needs a GPU"), "{e}"); + } + } + } + + #[test] + fn a_pauli_sum_held_to_a_term_cap_keeps_its_bound() { + let qasm = "qubit[8] q; rx(0.7) q; rzz(0.4) q[0], q[1]; rzz(0.4) q[2], q[3]; rzz(0.4) q[4], q[5]; rzz(0.4) q[6], q[7]; ry(0.9) q; rzz(0.3) q[1], q[2]; rzz(0.3) q[3], q[4]; rzz(0.3) q[5], q[6]; rx(1.1) q; rzz(0.5) q[0], q[7]; ry(0.4) q;"; + let exact = go(json!({ "qasm": qasm, "method": "statevector", "observables": ["Z0 Z5"] })); + let want = exact["expectations"][0]["value"].as_f64().unwrap(); + let capped = go(json!({ "qasm": qasm, "method": "pauli", "observables": ["Z0 Z5"], "options": { "threshold": 0.0, "max_terms": 40 } })); + let e = &capped["expectations"][0]; + assert!(e["threshold_reached"].as_f64().unwrap() > 0.0, "{e}"); + let (v, b) = (e["value"].as_f64().unwrap(), e["bound"].as_f64().unwrap()); + assert!((v - want).abs() <= b + 1e-9, "{v} vs {want}, bound {b}"); + // Uncapped at threshold 0 it is exact. + let free = go(json!({ "qasm": qasm, "method": "pauli", "observables": ["Z0 Z5"], "options": { "threshold": 0.0 } })); + assert!(free["expectations"][0].get("threshold_reached").is_none()); + assert!((free["expectations"][0]["value"].as_f64().unwrap() - want).abs() < 1e-9); + } + #[test] fn pauli_answers_carry_a_threshold_ladder() { let qasm = "qubit[6] q; rx(0.3) q[0]; rzz(0.4) q[0], q[1]; ry(0.9) q[1]; rzz(0.2) q[1], q[2]; rx(1.1) q[2]; cx q[2], q[3]; ry(0.5) q[3];"; diff --git a/wai/wai-quantum-service/testdata/es-number-tostring.txt b/wai/wai-quantum-service/testdata/es-number-tostring.txt new file mode 100644 index 000000000..70e279706 --- /dev/null +++ b/wai/wai-quantum-service/testdata/es-number-tostring.txt @@ -0,0 +1,3385 @@ +0000000000000000 0 +8000000000000000 0 +0000000000000001 5e-324 +8000000000000001 -5e-324 +7fefffffffffffff 1.7976931348623157e+308 +ffefffffffffffff -1.7976931348623157e+308 +4340000000000000 9007199254740992 +c340000000000000 -9007199254740992 +4430000000000000 295147905179352830000 +44b52d02c7e14af5 9.999999999999997e+22 +44b52d02c7e14af6 1e+23 +44b52d02c7e14af7 1.0000000000000001e+23 +444b1ae4d6e2ef4e 999999999999999700000 +444b1ae4d6e2ef4f 999999999999999900000 +444b1ae4d6e2ef50 1e+21 +3eb0c6f7a0b5ed8c 9.999999999999997e-7 +3eb0c6f7a0b5ed8d 0.000001 +41b3de4355555553 333333333.3333332 +41b3de4355555554 333333333.33333325 +41b3de4355555555 333333333.3333333 +41b3de4355555556 333333333.3333334 +41b3de4355555557 333333333.33333343 +becbf647612f3696 -0.0000033333333333333333 +43143ff3c1cb0959 1424953923781206.2 +3ff0000000000000 1 +bff0000000000000 -1 +bff0000000000000 -1 +3ff0000000000000 1 +3fb999999999999a 0.1 +bfb999999999999a -0.1 +3fe0000000000000 0.5 +bfe0000000000000 -0.5 +3ff8000000000000 1.5 +bff8000000000000 -1.5 +4059000000000000 100 +c059000000000000 -100 +4341c37937e08000 10000000000000000 +c341c37937e08000 -10000000000000000 +4415af1d78b58c40 100000000000000000000 +c415af1d78b58c40 -100000000000000000000 +444b1ae4d6e2ef50 1e+21 +c44b1ae4d6e2ef50 -1e+21 +4480f0cf064dd592 1e+22 +c480f0cf064dd592 -1e+22 +441aabdf2145b430 123000000000000000000 +c41aabdf2145b430 -123000000000000000000 +3eb0c6f7a0b5ed8d 0.000001 +beb0c6f7a0b5ed8d -0.000001 +3e7ad7f29abcaf48 1e-7 +be7ad7f29abcaf48 -1e-7 +3eef75104d551d69 0.000015 +beef75104d551d69 -0.000015 +3eb0c6f7a0b5ed8d 0.000001 +beb0c6f7a0b5ed8d -0.000001 +3eb92a737110e454 0.0000015 +beb92a737110e454 -0.0000015 +4340000000000000 9007199254740992 +c340000000000000 -9007199254740992 +4340000000000001 9007199254740994 +c340000000000001 -9007199254740994 +43f0000000000000 18446744073709552000 +c3f0000000000000 -18446744073709552000 +3fd5555555555555 0.3333333333333333 +bfd5555555555555 -0.3333333333333333 +3fe5555555555555 0.6666666666666666 +bfe5555555555555 -0.6666666666666666 +3fee921dd42f09ba 0.955336489125606 +bfee921dd42f09ba -0.955336489125606 +3fe886594af4f0d8 0.7664 +bfe886594af4f0d8 -0.7664 +3f7a36e2eb1c432d 0.0064 +bf7a36e2eb1c432d -0.0064 +3f1a36e2eb1c432d 0.0001 +bf1a36e2eb1c432d -0.0001 +3ee4f8b588e368f1 0.00001 +bee4f8b588e368f1 -0.00001 +0000000000000001 5e-324 +8000000000000001 -5e-324 +0010000000000000 2.2250738585072014e-308 +8010000000000000 -2.2250738585072014e-308 +441ac53a7e04bcda 123456789012345680000 +c41ac53a7e04bcda -123456789012345680000 +4450bb448ec2f608 1.2345678901234568e+21 +c450bb448ec2f608 -1.2345678901234568e+21 +444b1ae4d6e2ef4f 999999999999999900000 +c44b1ae4d6e2ef4f -999999999999999900000 +dc1b77ae0bf34dad -4.9911105725155504e+135 +64f0eeb9026e6076 1.715373926431966e+178 +7b07ce91e5906136 4.4251604027143375e+284 +305f050c368dcc74 1.0715660391465826e-75 +2ceb16e0a1c54aec 2.5973481493288907e-92 +97101dce4e7bfb79 -1.3475090132806154e-197 +9ad2e144d6e8f2cf -1.81996730402717e-179 +d9aa792e1af470ea -8.750186241947517e+123 +ddaa4e85b0d6e28b -1.603964615428183e+143 +8f8ea9d349428d8e -9.643915712060552e-234 +08f474ffb8e8ab15 1.5860846119992697e-265 +2ead854756d71f03 7.597954524892738e-84 +55bc79f8ada711fd 1.0204796945730538e+105 +0e1fc49bd63b809e 1.1910593176322935e-240 +b92199e83f5a101f -1.694933913128696e-33 +c5765079fc5d43ff -4.3162252145519944e+26 +353cfc387dfae6b8 3.0261999441573203e-52 +a32edabf5585bd75 -3.238696681491661e-139 +fc5639b16b714b4f -8.663725837306241e+290 +92fb2dcfc8ae9a19 -3.0797526843217145e-217 +544b0ec76d00616d 1.15589923121874e+98 +bcbb9b7e9a319aef -3.831296467316889e-16 +0f1a50d59c0aa21a 6.466053826432699e-236 +80ae2120826571de -2.1452846540129615e-305 +0e1ecd02ed7c0cbd 1.1547975125429853e-240 +0d0981e8c1fa7be4 7.296267179458751e-246 +dc86b3d3cdbdf613 -5.2803264785067434e+137 +6d0844c269e7693f 1.6732138965686944e+217 +3681da7f6993082d 3.909076791246025e-46 +5b928e2c987d857d 1.3170664051174186e+133 +6c716e1e6ced8137 2.347135155778617e+214 +ccb870213498f7f5 -3.92703626520675e+61 +9f07b27a78869b5a -3.3710794864173703e-159 +b0ba91e47f6200ec -5.874229071700385e-74 +c72b4c36d0db7fed -7.086891063466728e+34 +35f305b0c0fc9252 8.134710481112139e-49 +7ac78fb373ffbff6 2.7371952383912e+283 +8e39b81de71b7d09 -3.857101171397437e-240 +59a69ba126cd29b3 7.472552783215012e+123 +bfcbffff0cbcbf20 -0.21874988672197393 +2d1ce28856d20e5e 2.215603731797175e-91 +572a15ed48b3fdc2 7.841697833625344e+111 +32b911499417aab9 2.3803041954013153e-64 +17a8ec88e0da0bac 1.0669643833474769e-194 +0cc88eab2f9ad4bb 4.3902997915267284e-247 +bc3a5b419a43afd2 -1.4287870889761718e-18 +6f0f3414c47c9c0d 9.239942182940311e+226 +e71a7567edb8c675 -4.6049449625872434e+188 +63674cf841ee8ab9 7.034866163903114e+170 +e375837da840d9ec -1.2990664698521692e+171 +578eb22e1ea5a35f 5.905686629582816e+113 +4efa582ee029dfd9 2.9091650704676072e+72 +f3b363e938295a26 -2.1692042628035588e+249 +2e3a4ef3496f4112 5.2900001119406624e-86 +b809d78e2a2f9b10 -9.49285634690637e-39 +46217f104eb30026 6.93101536724741e+29 +8947b5fefda6afa6 -5.882775229507853e-264 +4a3ae29b2af0cb79 3.9292734840969675e+49 +bfd0273b10a6d4af -0.2523944532980051 +ed0a00eb302b52c6 -1.7928344098460697e+217 +3c0a45ad6d0175e3 1.7802719962921167e-19 +5bec25a986aa6fc8 6.393254796400403e+134 +904bc2dda0ebc917 -3.576279648101772e-230 +459ae6d82ef0bb45 2.0814210227308495e+27 +d8f30b16630d2b73 -3.0734116139155754e+120 +7110b72632258de5 4.251812196359382e+236 +543b14bc79e21fbe 5.7844666878803994e+97 +b3afb674d263f401 -9.867437071726851e-60 +992a5514fae813a9 -1.891221305366931e-187 +65950d0578f009ce 2.18377227571136e+181 +5703572bfb805a5d 1.4535047277321816e+111 +f58b6a5f9c786da9 -1.6465799069278203e+258 +a837793e9e8ad732 -5.957466131998648e-115 +50eabc7287dc4e1c 6.340262899598194e+81 +4dca1d57ff03f180 5.500381791581099e+66 +4899ff11c2f79663 5.661506902655086e+41 +16166c76ad45818f 2.860815981281152e-202 +cb74ca30b3d6894c -3.186028444038842e+55 +217355871886f75e 1.5120492207884386e-147 +28a1e2393381ed30 5.809632407259761e-113 +074c31b6d9efa2ea 1.6286721492574822e-273 +bec4688d35b7872f -0.0000024328714240831068 +f8fd0016351ac3e1 -6.275367084072634e+274 +2a3e36405bf02e26 3.29321318659287e-105 +4fa794552d7c87fa 5.332638378877033e+75 +6b65fa68c812c075 2.2579710060776677e+209 +b0d838d4f45658b5 -2.1420571626728534e-73 +f088256e71b67944 -1.1995980149915995e+234 +b4d1bf49868fc4b6 -2.89514964136048e-54 +7f30634d239b36bf 4.495301479005006e+304 +9adf073ca7e24f12 -2.9910257010919965e-179 +bf023d5f71c40f0c -0.00003478953860461099 +f8daff4ab7e1c412 -1.4604790788453035e+274 +3bb60a1f6449431a 4.667054448216033e-21 +e1b6cc3fce8a571c -5.128277780879042e+162 +974be547500504b2 -1.865902080785903e-196 +788637bdbb04623b 3.756003170106989e+272 +78b479878553843f 2.769071305261943e+273 +f614dc4037d1ccf7 -6.414723702314457e+260 +ab5917a81f0fb2ae -7.17007679825171e-100 +107db970f61ec64b 3.063346448760151e-229 +a7e591e85a840907 -1.7107275877582857e-116 +42e87faaebb9a0d5 215492859907334.66 +7cb7d0a0e9b446d4 5.941355978696207e+292 +93b411557b1e1b59 -9.314100426966256e-214 +17282941775bd12f 4.040271440024106e-197 +1d6eb56cc3fecd4d 6.5095912305809035e-167 +9cc6d38ebb4dc397 -4.725304702312625e-170 +93e7f09c76995dd0 -8.889046505961924e-213 +40481181f54f1b6b 48.13677851069118 +fc10536a39343f9d -3.977510832916913e+289 +2b59a11006fe10a2 7.323449055534529e-100 +b25de04b53658403 -4.43267188156626e-66 +482f7b77f3b42fcb 5.3564356221290984e+39 +aa19ef48de09d554 -7.067451838786451e-106 +bc1efbb4d09613fe -4.199010356643058e-19 +01640cfd5e7e0059 5.847695999398785e-302 +2103a6ef6f7bca19 1.2007177480205567e-149 +ba59dbbf218e9bcd -1.3055148249443392e-27 +47cfbdd44466d5ba 8.43834345315447e+37 +0f7180ea42ce3691 2.752517763735277e-234 +1d57d70abe6c2ebc 2.526788028536205e-167 +3df4865f060ed9e1 2.9867642218664106e-10 +9cc2d942d8ac9c12 -3.9018746502228826e-170 +68c29c22e6ec81aa 4.347217825559248e+196 +a1cd5d166c667229 -7.348551753937433e-146 +950c6175be7d148d -2.762460922630013e-207 +3f3e42bdb31d6de4 0.0004617417463259317 +b8e8620efee2ae3f -1.4675009188927917e-34 +6892f24f073504a3 5.532311359466528e+195 +7faf4d50b83a266a 1.0990510499741633e+307 +36eff6fe424288a6 4.479224922930724e-44 +7436a02ab71ee437 6.479738675343636e+251 +833a63ba9131343f -4.131963459956029e-293 +992cb64cfdf7b997 -2.0621386115069104e-187 +c8b93e004898d324 -2.1989050319881327e+42 +645d2dce235dab82 2.8867342589916004e+175 +808a9a75bdabb055 -4.735555107626997e-306 +995531f7eedc5275 -1.217811298351344e-186 +087d63039379d791 8.900118122652927e-268 +f3c9caffb024e07e -5.770954824128418e+249 +ea06a464360f763e -5.546063542174503e+202 +53361012dddd2752 7.190830824723893e+92 +d66a74b438fd099c -1.941648404758352e+108 +8d5497b6e7e3148f -1.88493094519322e-244 +00252f2c32b41166 5.892070161490275e-308 +f4dba3db8471e0c4 -8.105792111531084e+254 +a447b4c07713b205 -6.523059653436162e-134 +58d68ff52d13b421 9.103372873476245e+119 +7c3d90428332bb09 2.8810573598215993e+290 +98e09052e5853c3f -7.435185103913208e-189 +b90814763588d987 -5.797031974500124e-34 +04349ea8330d49f4 2.1158634801682737e-288 +213e49f755c9ae67 1.480492072589796e-148 +adc15b82753344fb -2.726700493055371e-88 +69a0dc20acf37cb2 6.45269948043362e+200 +729bfdea5375f6cb 1.1945606721328604e+244 +f8eb32047bbbcfe6 -2.9423966222416894e+274 +4b85228ee7b181f9 6.477856523538338e+55 +41031bc75f55bcba 156536.9215502495 +2da461eb41957943 8.004766223603229e-89 +d74e810a9b826371 -3.667973268328953e+112 +1cdd495915ad9bf7 1.2125234815256259e-169 +baeb7806c59edd00 -7.100532164112103e-25 +3ba89ff13576a0ba 2.6072493943615606e-21 +005ca96b1855237b 6.377460053650196e-307 +c7b1d77110b837fd -2.37155129898773e+37 +69bcfe5aebcf18d2 2.2193146864212394e+201 +6f51d1ecaa02f263 1.688592499278879e+228 +6d8dc769b3280f47 5.256011819884271e+219 +b51767a476976e99 -6.108951742016336e-53 +0418c241fa7ac604 6.351467798285668e-289 +a581230adaef3288 -4.944515222634075e-128 +f1ac9d290f1d4eed -3.726522459630376e+239 +3dabc8a9eded6f30 1.263462896392155e-11 +55cb59e89b5779ee 1.9603110021981586e+105 +72a54713a9b86c9d 1.8160593387967868e+244 +a3726687db6b9b04 -6.18061749778593e-138 +5fdf2267673f0d32 6.52256133253205e+153 +68dfe63578c47fa8 1.4903137223520643e+197 +624e220196711d57 3.4704648912517096e+165 +be9e598ebfea4aad -4.52247775153582e-7 +d79af3ca3ba29578 -1.037084836001061e+114 +6f5e5868097b8e52 2.8754690828576924e+228 +5af01374addcadce 1.1143358899359637e+130 +2faaf7e9e83fa715 4.5489015505785396e-79 +bb42e4b2bad2bd1b -3.1256429291924854e-23 +d30563d941e93ea1 -8.714453691721558e+91 +1167ea9167c9649c 8.076568837115626e-225 +b6249a7c39665155 -7.048729021186279e-48 +b3f56e3e83c468b7 -2.1338113414315463e-58 +a87bdea4d9832da6 -1.1317069599854738e-113 +e437318091b5827d -5.736425033777084e+174 +59e1b682e1cbd639 9.367392914269608e+124 +7d32511b3390efd5 1.1698384100740314e+295 +1d70ed6acaa29b4a 7.176504206107081e-167 +1e71c6a67d004cfc 4.938963803661829e-162 +a0c0b3f99ebcf365 -6.378335818567057e-151 +498e468dbb57f3c3 2.160539023382998e+46 +1c54594f126dcce4 3.290960217035386e-172 +16ba1d39f25cae7d 3.4116072635664438e-199 +67de1f8cd39e2861 2.1474297741370636e+192 +108e0b8ab05d2c71 6.192795587813568e-229 +96288ccb432aaa69 -6.264176696201658e-202 +887013f1c0b1457d -4.86937695727198e-268 +e47a782d96a0d8b7 -1.0474717780590377e+176 +c0f1c836d72154c6 -72835.42752202143 +01ea4c72979ce7ef 1.9634838852369833e-299 +e58cc45382b7c5e0 -1.4921087049120546e+181 +0f265892d429d26b 1.098128118575969e-235 +119be96337a67b0f 7.540654791281066e-224 +ad7f4ee4ff441439 -1.5369479028680968e-89 +69765cdf019ab251 1.069849989958128e+200 +6c83796be9953375 5.244798957954977e+214 +b30aa29935c26453 -8.093307970654829e-63 +88c5405c130c945b -2.0595934067202045e-266 +9ffcaab00de2fbb3 -1.3362898797649858e-154 +f60b6a392f39b284 -4.215154408802224e+260 +34f99df66499e0e1 1.6715899149649308e-53 +b8c6fbae04f4cb60 -3.458116161801038e-35 +ddf1a401094ffaf6 -3.4418528054556186e+144 +24e629d896fb1883 6.244957363033692e-131 +e2fd7570a6e4ae72 -6.948513516814706e+168 +c21511f61c10bbae -22623913732.18328 +6b4a433781c1b159 6.745344960809089e+208 +f7b9bb632073447b -5.3101816399799955e+268 +28f350082a0bdc33 2.007626461224594e-111 +21b037be8db8a74b 2.0293303658808273e-146 +f4827d0343e964c5 -1.6943537191920942e+253 +18fd760add37274c 2.6449150601891564e-188 +1cbe96ad55a91a02 3.1660794797916675e-170 +1b376669576888b6 1.443641164988039e-177 +862f25f826bcb427 -6.863863281173272e-279 +608edfdda646248f 1.324668934962616e+157 +b98511e79e1e6b06 -1.2985353359528908e-31 +384ea650b7785650 1.801425646361179e-37 +9ee03c4ca73132fc -5.774031821847077e-160 +6facd3230b5e6f99 8.740420174212429e+229 +b27b8d6c7d2c1506 -1.6351626804149013e-65 +9fc9762bf52bccac -1.4836042955288556e-155 +915133d1f1cd3035 -2.904643624130628e-225 +01a2553b894e0715 8.55473282230542e-301 +1727f4259189fe5b 4.005580121434914e-197 +e9410ea4b1d01b67 -1.020034607560592e+199 +ae5bac1c8afb8291 -2.2257001888640823e-85 +c01d77809ef4f1d4 -7.366701587391123 +131dc70966a1ed37 1.3496811336203614e-216 +8ebed168e7eb032d -1.1831722723762523e-237 +8027b30dd057be6b -6.591622639274444e-308 +7c80d6befe7bebd7 5.251196760198493e+291 +285d676d738709c0 2.9850270767861166e-114 +d4a6fbbb463c77d3 -6.283787146640815e+99 +6b7252f506af9bfc 3.76511719197651e+209 +76e7c3b3c3adbbcb 5.986516557134494e+264 +beef68cb1cc5727c -0.000014977144836941481 +9ddbcc9adadfe798 -7.542846590581299e-165 +e85d4e2fd8cebe57 -5.3481691003979895e+194 +49a34268cecc56eb 5.497590193813714e+46 +2fa1ca340f951486 3.000707082751453e-79 +1c68c5ee9e01df2f 8.012980891056525e-172 +7cd94d5d160df751 2.5249576968497247e+293 +415f292fa04818ff 8168638.504400491 +f40bf4be400d570e -1.0007874993533206e+251 +82042eb409794e20 -6.027370236351834e-299 +1255273c7f8434bc 2.3407801788530437e-220 +2ce50c5e474b93d5 2.0181277076171575e-92 +be08af88f83f51b2 -7.184479543359204e-10 +2d725bb40b0f0391 9.012219815681637e-90 +c0085237f307d9d6 -3.0401457773943816 +a9c57d5561e763e5 -1.8300357200434082e-107 +1bf5b3cab644f462 5.484139778266072e-174 +5547a5fa668d250a 6.620784023388953e+102 +914833a9ee283dc0 -2.0432482406064233e-225 +7ef314f60b351dbb 3.27142367096342e+303 +00221d9d7b537940 5.038591926334749e-308 +60336535e5378fb2 2.600491761804936e+155 +e8cc97f0aab24c2d -6.679358410377362e+196 +b581ba40a91b83f5 -5.922695967925887e-51 +fbb60192cdb3e9b2 -8.377218977198061e+287 +9adaa35dbc26a2e1 -2.5678321808765053e-179 +22cdc3a4356277e4 4.881649701615915e-141 +f488e151ff7fca0b -2.280129402960264e+253 +df756f66d48cd75f -7.016566063885543e+151 +7f45c76b5299f931 1.1948219385408311e+305 +3ee3c6ab5062a683 0.000009429956218848283 +c54654890ca7a30e -5.399114986379544e+25 +b8275d981ca7efc8 -3.43326668751501e-38 +be5287885cb25217 -1.7256871303970305e-8 +79729ebc8c3b5373 1.0314714448346568e+277 +a0bb6616a3b39915 -5.231346146989085e-151 +4fe9636427fe1b67 9.186744261462331e+76 +bbeb4ae36e79de91 -4.6235349042064457e-20 +41d653635fd8a96c 1498254719.3853407 +137fb828fc1cc33e 9.201269076608807e-215 +407a3783d8c3f538 419.46968914552735 +b6827a52e88c3cd2 -4.045773658468768e-46 +9620649d499e502b -4.182847390501118e-202 +cfed170dd20a064b -1.0526244381401319e+77 +05faa3dbcae8e087 7.338029340244412e-280 +e3456f983a59f086 -1.617966873078957e+170 +462e4081e78fa2e7 1.1984044805017534e+30 +8e33e7613b30e462 -2.9849706507009475e-240 +0c342c6093a7dd2a 7.044036567231477e-250 +147bcfcd70f29810 5.287264256000436e-210 +a7786e7e77f77920 -1.5138189353600061e-118 +3461fc042646dfd2 2.2920690566792716e-56 +7f8c72b2a99de6ed 2.4971115149058146e+306 +d7a5bd1b9baac660 -1.6729462719308328e+114 +e722fe3209380bec -6.611191638219227e+188 +554332c123a300fb 5.37490011647211e+102 +724d72ee194f183a 3.9273198859217023e+242 +fce07bdde4a8c88a -3.289914141038722e+293 +13f028439b74fb9b 1.199862179421384e-212 +41e31784775794ac 2562466746.7368984 +5fed0fcf79c39085 1.2176702828593951e+154 +6d8502bfc80c96e4 3.7084137376521155e+219 +70db12824bf7b6c9 4.3038962269505593e+235 +8838c5e2d39ccbe4 -4.689238841098948e-269 +bcf946457e938b73 -5.6120659011024115e-15 +8191c98000a810a5 -4.1500155236360685e-301 +addcdb75bb31c9c4 -9.066432764855255e-88 +274842d113a35b57 1.879059262491734e-119 +a7842014627a2821 -2.493987738833499e-118 +61c683627952f431 1.0128504205874678e+163 +8368f6e64ca97d99 -3.1270602502160887e-292 +32945cb557986922 4.833752484941355e-65 +5f255fb874e95170 2.18641096249842e+150 +1e35dd72abdedfd2 3.796928824582299e-163 +dae27a90cf1ed6ed -6.404441427080342e+129 +93f0e46413e8f000 -1.2544346808024838e-212 +b3c8e1337c3321e0 -3.0965340499603544e-59 +09e1b7d60d793fa3 4.50143683046821e-261 +be4ef12552e0451c -1.4408480266571558e-8 +7f35de212ddb4296 5.998444056935152e+304 +f5a2fa6852249193 -4.559322011621158e+258 +f4231ce12176dc70 -2.7368496778234114e+251 +1bec8ca43e9d2dc8 3.6071916608625575e-174 +7dae1345bf346593 2.4586429291853966e+297 +ed6dc22866d10998 -1.3130971691950527e+219 +6b5dd9b4387bcd8b 1.5333710285319394e+209 +c1c7e69446e938d0 -801974413.8220463 +b444a2f44e72dea1 -6.575181091649925e-57 +1e82dc770735b35c 1.0480969455622343e-161 +0c5700918c488f3a 3.2127236365522586e-249 +9b68e2538ddc90a4 -1.2281580878808662e-176 +2bb34920cefd8085 3.5269220213870295e-98 +beaf7e7f7598fac4 -9.385982863775702e-7 +f219b5c60677fa31 -4.2858750911479046e+241 +aaede6659bc9b985 -6.6748650773279286e-102 +39603cf742ccebb6 2.5018828229476747e-32 +6c8c2d0d3dc75fe1 7.588326466543165e+214 +4bbeaa8eafab091e 7.5193400998407425e+56 +dcd8d3ae880dd88c -1.847822250010464e+139 +7674825ead00603d 4.036325359191039e+262 +b55191dac33fcfbd -7.33746758445045e-52 +be389d55be79ff62 -5.731030739927105e-9 +b15667e29cae941c -5.072493714244658e-71 +bd6e217d3cd94e34 -8.563693320839682e-13 +7264b7803a65f1a8 1.10511939020479e+243 +d607a31dbdce504b -2.710591128553894e+106 +9b3aa2dc437ebe2b -1.643290714711593e-177 +1f320fc3a0bca997 2.0555024535076202e-158 +8030d4e937ed5504 -9.362934810703572e-308 +c879c11e292b4eae -1.4022010848201058e+41 +91dcdccdd11973b3 -1.2476058333898762e-222 +d217a900b9b9cd94 -2.9416877464097773e+87 +85ee0f5d59155b0f -4.140028050350901e-280 +1ae3b417a9625279 3.7987057951517534e-179 +6bcaaa6fc5d1289d 1.753319546067792e+211 +861aca76501b0d8c -2.9518264098121837e-279 +8e011fe66ae7d897 -3.210215315206214e-241 +fe99e0b2411ad2e6 -6.932049913855929e+301 +ac8ac52104f69ec3 -4.010516488826794e-94 +852fe975eefca33e -1.07301926397444e-283 +d4f70adf859e55f8 -2.0159866460463426e+101 +72f68ce6b9191753 6.159006229399942e+245 +bcc8aefb33be91bd -6.851053208151698e-16 +667e1789bff623de 5.1145521953002703e+185 +d9a914c26548f819 -8.290002534693647e+123 +8f7063a533ed4fa9 -2.5772834504821396e-234 +1aa49a0b95c05f76 2.482427036430878e-180 +ce33d1477d82c248 -5.342786650285256e+68 +a4669f8a601855cc -2.4900385116411694e-133 +8f240cc754ba9667 -9.852943062726685e-236 +3198d369936c9a8b 8.99258986878193e-70 +3e2e1abd0ac1817e 3.504618892007664e-9 +6da6a9a5cb639dfc 1.5999935453086858e+220 +d03363b36023a5c7 -2.2451455966720522e+78 +b66d70cc9c2a734c -1.6115272866854186e-46 +0d5a8941a8b374aa 2.4289667843976393e-244 +ee948a7b982c78c3 -4.7520092168535256e+224 +e23d98b80afe70f2 -1.7043502628229411e+165 +c820da07ea4ff093 -2.8671653361557315e+39 +1cc7f7eadf112bb2 4.961717144101795e-170 +515f0e77a159a565 9.426956490206587e+83 +02a08015f200ef6f 5.045997475309083e-296 +e6d5f24ceed0d571 -2.38727428857913e+187 +f6bee465c8a0089b -9.727578605964799e+263 +59f06d8b36b40e4a 1.7375623958024647e+125 +467f916c65bfb4d6 4.00172705416029e+31 +c6faf1e6587d3e3f -8.7441268062409e+33 +f4cfee177b43ab83 -4.6819405504407186e+254 +3fe4fb22a807ac14 0.6556561738810864 +ff3d9d0255a6264c -8.123116522595069e+304 +943714830e4d7900 -2.742339407413927e-211 +010a53ff73cba3f2 1.1997594436899031e-303 +e61941881bd08835 -6.70720608602141e+183 +eb5bcff0e6098465 -1.4286749772672437e+209 +a2aff2c7e9722e2d -1.3099679192652549e-141 +9fa21e2bbd8ce131 -2.639240924934114e-156 +934d8c9a3c8f94b3 -1.0714681438820776e-215 +cfa0c5965731c75a -3.793056030901647e+75 +80613564ebad3254 -7.658068373339475e-307 +c102edb9951c7d30 -155063.19780824473 +f4c4e9ba3d1b09ca -3.066497746641479e+254 +fbaa947174280d59 -5.0591610297195054e+287 +4f75c951cb6e2b03 6.158919128871474e+74 +680ba3f8d9385795 1.5763526541164746e+193 +bf108974a899627a -0.00006308340575723352 +6fdcf7e057a38e3e 7.02714977042604e+230 +891ea0bf036c86a2 -9.498636154058871e-265 +f9153f6c58c1b72f -1.8391152285324428e+275 +d47c48ecb9fa1f81 -9.666575821616858e+98 +6503a7900a022bfe 3.9822669149877094e+178 +9ae99a5bcfb11029 -4.936087213880973e-179 +e424cab406fd5849 -2.571223295326158e+174 +20765ba05bdd90b9 2.6680472451494126e-152 +4220e84815a925d8 36308257492.57391 +2dc62fc8c8560193 3.485324003153212e-88 +78b05046ded4a950 2.2063042138267773e+273 +874b1c59db6d5402 -1.5660853249681503e-273 +abfd9567855ace2a -8.656305713800473e-97 +976d4d4a274ab136 -7.839868932194627e-196 +3dd9eead6426a9d4 9.434127874546652e-11 +b5579e51c1701187 -9.863518680223705e-52 +7c76e4bfdc0e7064 3.569677734214926e+291 +1ba0014d852ef584 1.2638939447703403e-175 +5ea9b4d919f7496f 1.027187643380712e+148 +8d728cbe07411c3d -6.791757292376097e-244 +d0c197d0ea053145 -1.0430125447723807e+81 +18f524eef87bca67 1.8982589127936495e-188 +e93cae7cca534433 -8.575913736217201e+198 +debb4e79d3668e7b -2.182250795055045e+148 +8fe0cda453e2bda7 -3.382265071240417e-232 +49d787ea5666f11c 5.373512080270988e+47 +8921121732a9fbfe -1.0588252688337632e-264 +cd602031a6df9789 -5.307001533128124e+64 +7973d9ce554aeae6 1.099649151902254e+277 +0ccd0b4dcb6006b3 5.1924500362678124e-247 +5a74eb079cdd0a7e 5.663958993422249e+127 +a7c03b7eda3defea -3.2185076687360775e-117 +de1c95e748652eb5 -2.230923289202934e+145 +341d48c3e938e9a8 1.1663121214012188e-57 +e3214444d913f27b -3.258189233818417e+169 +d38e58fc3cac2b5f -3.165140511464378e+94 +e10e29bfe32644c9 -3.3130155193791e+159 +68909da0a9e81a00 4.851730825144609e+195 +f8cbe27605954a34 -7.542419838026915e+273 +d48e6395feca6da0 -2.0771368334899458e+99 +532571c508ee917b 3.4946432233028046e+92 +2a153d918e467299 5.788237157248829e-106 +083fa76b02ed783c 5.991728997626231e-269 +385ae3da9b292dcc 3.1609107057340923e-37 +83547005d8c38c97 -1.2800115518036446e-292 +c859468c0e9fce4e -3.4403329403613844e+40 +4ceb47a00c1ca252 3.506954061349979e+62 +d508c663404a4f96 -4.3351225290984545e+101 +82329c6120b9fe89 -4.446421964008685e-298 +5a1023aaed3e0f34 6.82815895648894e+125 +373bd9f34ad53e2a 1.2488985613794856e-42 +3b0cc65c03665ed6 2.9752488784232493e-24 +33f99ae766fbe7eb 2.549454200560016e-58 +790ce8b17ffa8ae4 1.2511186650233594e+275 +005397363f1946f1 4.35906909931827e-307 +06b941a56d90e83c 2.8495478911521364e-276 +c14e4b23d3ae46ec -3970631.653756013 +c2530bbef5872161 -327205770780.52155 +9b2a87b4a34e5763 -8.183733141991388e-178 +9dbe0d18356b730d -2.0384661263510292e-165 +3fe152dbbceac6ab 0.5413645448481551 +b78982e3da3bd9e6 -3.66069047838575e-41 +227582ddc7ef17d5 1.1025203990730335e-142 +5fa2aceb5c2b9cba 4.890567681172239e+152 +7bea5f27b9e5a503 8.031265523907269e+288 +5af8c3f5a64d4e89 1.716661499844696e+130 +b5a794d4db2b5654 -3.151373068712216e-50 +aceed0aaf97215f8 -2.9545751864874586e-92 +87211deead1c8fd3 -2.471949873650687e-274 +466ebce3e287220c 1.9482428425463875e+31 +dab5e4a43e5b2f48 -9.484753323678878e+128 +25be2ba313294b16 6.964079626492754e-127 +5ba0b12b8e3f1c00 2.3696178685894556e+133 +dbc5a1a571146238 -1.2283251162188955e+134 +b41f08cb7b51c4fc -1.2360144933463334e-57 +837069fdb1a3d875 -4.112059274829888e-292 +0fbff2061c322285 8.037699641936216e-233 +6fda7e47d7d24780 6.426765988698825e+230 +633b698ce202030f 1.0345301263900091e+170 +ce58966972b524c9 -2.651515770338092e+69 +fbb3c04f10005cc0 -7.518816736461121e+287 +2eaa5d555d5d6c79 6.785660674745272e-84 +8beced74dcf1e8e1 -3.1564978579077087e-251 +f2609a5b12ce1370 -8.856697976194829e+242 +bf0c05f073ad5356 -0.00005345001269200257 +e0e8cb7aea941c70 -6.80846356652124e+158 +ca7cea9469582848 -6.761801499634537e+50 +21601e6306b98a18 6.302924575221773e-148 +6e7b9963a18d7f0c 1.5962206143116225e+224 +987c1668e79426f2 -9.850004616824814e-191 +cb9ac796b3f2f23f -1.6415873573709347e+56 +11d8b917cc58781b 1.0686727282666483e-222 +6ecc41a6a68bae2b 5.229550422146821e+225 +2f8f1d8d9b8653b7 1.3121005950106307e-79 +6ae785060ef352d0 9.438813551554997e+206 +0fd06291d48a0075 1.6490385214738937e-232 +bae477ebc7c7a935 -5.290968111200451e-25 +4e6b140482cacb27 5.8402132922071786e+69 +8a1893678efb7771 -4.994952930922964e-260 +8dbfa9d76a6b7ddf -1.8549048802010765e-242 +e4522da548133ce4 -1.7984188159938745e+175 +33f98a0f26eaa39d 2.542902638237863e-58 +3ea2b2f7b548319a 5.572763748809117e-7 +1181a0ed00ef8779 2.381285493566852e-224 +54351b305d8ea637 4.508258399187813e+97 +9bfca215272cd6bb -7.235547848912783e-174 +fa5bc3fddf2c41d6 -2.5200137670732935e+281 +e2f0ff993e12acd5 -4.009465238566373e+168 +432e6555e4c71ccc 4277834443099750 +2e66eadedeba21f5 3.6865300551300626e-85 +640c5808985d88f6 8.762879380786513e+173 +4df90fb6b3dfce67 4.2228344409869815e+67 +e02a6770c0f1083b -1.7701031091360108e+155 +42bd8ffb35e384eb 32504232141700.918 +2b8ffb7df9b01922 7.311093926912118e-99 +dccf5e018e417190 -1.1673038746038174e+139 +d54d17711dd59773 -8.144711304657227e+102 +92080269395f809d -8.302613832239405e-222 +cd7136b53d66b8dc -1.13300906943579e+65 +56076d685cf3c2ad 2.6865324583713083e+106 +deaaa3e5943f2e68 -1.0645001463920513e+148 +f142d7632179ca34 -3.8340587213042945e+237 +76acee34e94f34a0 4.554933970975543e+263 +a54acc72305682c9 -4.8326379157139424e-129 +fbe520f36ad7bd8c -6.43456109571911e+288 +2921278b2db2f1f7 1.426621747263482e-110 +c8b9f06f1ac309d4 -2.2596225996181304e+42 +017b510de0807ac7 1.593350359740951e-301 +da03f0c983e32bf2 -4.218187534084529e+125 +4f3bef942f1a5125 4.935838315850994e+73 +c3ebef4a927a8cc7 -16103276411517024000 +cf8128b49859a81e -9.701548412753666e+74 +c2d330c96b9cdcce -84401034523507.22 +b680824ba80b16f7 -3.614685937511092e-46 +6eccda24e5745d1a 5.339794973034318e+225 +586da2121c4ab320 9.340825202162434e+117 +9147472da036ee46 -1.965258419085388e-225 +14cbe0c68f31d21a 1.6959579933038423e-208 +46877f721a64773e 5.95740888401159e+31 +74b63d13890ab050 1.6304346543808809e+254 +7b2711d7d8deb130 1.715251685002469e+285 +3a1cfcd316414052 9.146872466519686e-29 +8283834824939652 -1.491812728218595e-296 +56a4973f290965fe 2.4179194113359926e+109 +2c3f8781a8d7c8b5 1.47610082212025e-95 +868ae33bb3aaea64 -3.791984215981586e-277 +4790a8133e5ba6b0 5.535086027771425e+36 +86b81e7681e2bdfd -2.721217220723736e-276 +c3d0a4350c7da7c6 -4796566914205686000 +859b325917e66d09 -1.1705226721436142e-281 +831e936316d5c393 -1.1968537522579172e-293 +7965afaf0452ecd4 6.006628589628362e+276 +127f494ceb61fc0d 1.384834246808456e-219 +09b1531bac239cb5 5.5018408673337e-262 +263042c64d9256cc 9.608702266933024e-125 +9af184e86d874161 -6.755167932890944e-179 +0bac390a5be637a3 1.9247483900537996e-252 +5cb088c29630730c 3.076533954390552e+138 +5a52a97230b550ea 1.2632636922852754e+127 +2216b180d20b40cb 1.8173606497149155e-144 +38e2945a3072048a 1.118213052237986e-34 +695cb1f5dc048203 3.4319880244959657e+199 +625f83d971526bc7 7.2593117784598354e+165 +5a51500d67f2ded0 1.1719338685793903e+127 +caa5e9abc48a8f6d -4.0992716521136025e+51 +174e7083222be133 2.036059043437772e-196 +89330d8ce6d39a31 -2.3635544721276753e-264 +c1bbe5091a349145 -467994906.20534164 +5be6dddb05420927 5.193858519813684e+134 +8d4e43d5eb0424f5 -1.3851409423197001e-244 +6b2e9dc366bdb1fc 1.965889933152688e+208 +000a2eb0921d359f 1.416034466407118e-308 +4c422105c2af8834 2.275950812559033e+59 +b0b77bfe27865a24 -5.1920583091640985e-74 +0396cc464e3b5f90 2.284546280512328e-291 +a8be768fbddccd2f -1.9792295478435672e-112 +dacfc655f2dbdf75 -2.7531526098309324e+129 +c7bc5ab0bf31158b -3.768927545110587e+37 +7febf8bada7b7560 1.57138610536333e+308 +b599e5cfe8aadb8a -1.7304617122023073e-50 +39e455935fc62cbd 8.020459264135397e-30 +961e08dd122d2fa4 -3.8318201020485495e-202 +1430aca1bf401cfb 1.981215810834074e-211 +a5598d32aba2c202 -9.215506078463678e-129 +da3adbfcf0094706 -4.545412933627047e+126 +1eab89c68b485408 6.121074341438089e-161 +9371c38b538ac6a0 -5.152999167844173e-215 +fb05563909127b2d -3.9660242449037684e+284 +b68b120319cd349b -5.927133380694498e-46 +1ad373991a44e832 1.8750675434372717e-179 +54a958e87e882d62 6.930111875654036e+99 +00652c3342a225b8 9.422145196776628e-307 +a17712ddd7ff0ff3 -1.804510150070046e-147 +a14e15a8a8356d2c -2.9410096972856384e-148 +8e624598c077ccf6 -2.1921802649765537e-239 +3906d418a844deef 5.495761765582788e-34 +7766ed6e0ada0c92 1.478570791372786e+267 +df44f9d099c8dc0b -8.582764396920564e+150 +6e86b5a60fab2f73 2.626852002192381e+224 +98358998474bc5ed -4.720604965754988e-192 +31b33760cfc58926 2.784269373625963e-69 +767b0cfc59748bb4 5.3237193709695084e+262 +8a7195f972d40fa3 -2.287529932628886e-258 +f4164f58ea3e2f7c -1.5973313209434804e+251 +6cf08ba709ca0c22 5.703707582861887e+216 +b9e98bf6d5aad0ba -1.0076410307016102e-29 +76446ea4df94eb9b 5.026450960660305e+261 +531e0878677a448c 2.4471391660995518e+92 +de0925f627be1305 -9.81332867027777e+144 +e6c542e9fad30e63 -1.1563753633139795e+187 +d494b51a5d5750bf -2.830771813956538e+99 +60283d11b59231de 1.6249292131758703e+155 +582c7729f034a23d 5.607985821264655e+116 +601eaecaf2c9e439 1.0284721948397653e+155 +4a49acd65ab059b1 7.504853521088282e+49 +fdb26305bba17542 -3.0062288103555564e+297 +53039d9f38732728 7.991581704766963e+91 +1439e63a68e30b66 3.077312217575961e-211 +844fdd49ec7cd4f0 -6.539432018319294e-288 +2516d919d8bd1159 5.1502822998529505e-130 +ec5d450c5c151d3b -9.853613067504057e+213 +2a52f389d1cf19c1 8.263062195706041e-105 +688d2b06fdf6d7b2 4.258483588036954e+195 +92d481cc7a74969d -5.809327189377274e-218 +abd70e731c9678f0 -1.6866063833258376e-97 +f4f97f64c02d6801 -2.9909983508524332e+255 +43fc93418ad51291 32944984306792075000 +c689b6f3673e3cf4 -6.519438691744312e+31 +1af6666372fbfd8d 8.637135434971623e-179 +2fce968de73cc936 2.0637813384395746e-78 +d49841f2d0ae3d24 -3.3160923706791817e+99 +72bee5998998a85e 5.2741325329998766e+244 +31565c5754b64e8e 5.0622847918509404e-71 +e47133169d7b4193 -6.806360043572523e+175 +4d66ca3fe103b3d0 7.500222019963876e+64 +7dee24dd293940b7 3.9428167821609833e+298 +6df480a20ac1fff6 4.6319414969372505e+221 +fce882d342474189 -4.892011378700657e+293 +f3f6b50b87b68d4a -4.0644345303208036e+250 +f1f033114913f2d0 -6.751148946865206e+240 +8a739c49934d6135 -2.550893600244383e-258 +4ef1a556527b16b7 1.948594718131877e+72 +c921dc5e5878ad5a -1.9915473771057683e+44 +75b09def7a484a80 7.984123012957471e+258 +b64717bfcbd47a15 -3.1601429294459906e-47 +4a281327215df7a1 1.7592691378165458e+49 +951963cf8e8a840e -4.9426941739972536e-207 +1e67945446bb5286 3.275708875387406e-162 +604093dea1f445a3 4.445389786241638e+155 +1000e34aa2c2a5e8 1.3597150401510495e-231 +c488c35a66d55aa3 -1.4617553101467643e+22 +4c65d0abbdb638d6 1.0954858490306238e+60 +7b3ac6e00217a127 3.981763702334435e+285 +35fe6c91fc9527a5 1.3010634833719295e-48 +eed63607d78544aa -8.22140409617974e+225 +ea9bc2b2d8a824a3 -3.4814887255652016e+205 +96c1259cebd03c2a -4.480217934235911e-199 +08381e8c8c29d6d2 4.5655085683002545e-269 +b3d1172b466071ff -4.254178718032956e-59 +be271a86db772edc -2.6896153878072563e-9 +011af3c801d3f781 2.4564036721371386e-303 +37f603aebb81902e 4.043403930008548e-39 +5082ba59858b588e 6.939347094995094e+79 +343c67726439adbf 4.5250324788781247e-57 +a6e6f9f819655124 -2.780574478187178e-121 +eb687d6fbf2b5286 -2.5160284479318013e+209 +e633803d0b9765a3 -2.071537120553806e+184 +0502b8d7ffbd43a8 1.5737922830736846e-284 +bde05961b139d32f -1.189556999890527e-10 +5891fb0d10d88b49 4.534236138333109e+118 +1477e2c1bd8cc81f 4.5409033514351235e-210 +fc4a0356a858364f -5.0701050665204526e+290 +92babc68fe0af5e3 -1.8934734351259362e-218 +5d602b010267f8c8 6.161179332114448e+141 +4e06c6536cf60539 7.675062246760708e+67 +f2d55c8939ef8773 -1.4585710897319003e+245 +bf71b26469e9e4bd -0.004320518723596564 +02608a8e97bcb834 3.161566392972214e-297 +6684dfbf6a334c44 7.095668587650535e+185 +55b037e58604bbdc 5.811992829145083e+104 +6fbca890d00bc5ab 1.7379991066070355e+230 +24b2f82b3a2fd8e0 6.681219222762084e-132 +9be0c9336213bf51 -2.120890238588466e-174 +d3b4c79f2f0c6c6f -1.7338017617331192e+95 +13a513b3412a8f77 4.891262135218039e-214 +0a72a4aaf3b7e7a9 2.425073849370363e-258 +34095b9133104226 5.0496402215142525e-58 +0954314c8b662b22 1.0019717915178535e-263 +38a3465cd1766ff4 7.250394159114308e-36 +4b5dfca904e7fe2b 1.14887183329328e+55 +6e3a6c2426fadb17 9.551014611674867e+222 +09ac83ec9da10b61 4.5278515054912187e-262 +fc12a9116fdab137 -4.546291876478021e+289 +63a53efcbea5a995 1.0263266819532437e+172 +7d06eb7a148c2786 1.8297729475691867e+294 +5459f4007bce1e49 2.2174221721542543e+98 +fb12de612b1d3035 -7.014507896688034e+284 +2c7656f7c62ba715 1.6733972899344104e-94 +19b85037fd9a951b 8.940618230258263e-185 +b62e9e14ab8586f1 -1.0474672801318426e-47 +c3c275a8f01511bc -2660310028113901600 +86978870afb8d49f -6.637747517715821e-277 +2a0fda3e7fad62f6 4.340055506929527e-106 +db2a0d48cf3245b3 -1.4446658413610043e+131 +beb9a791817b2df8 -0.0000015291311736162434 +8a7c328ad24ba5a3 -3.667864367440803e-258 +6811717655e23a28 1.9895904625633026e+193 +cfc8c9b96e3a745c -2.2423848841686237e+76 +e03fa257f39897b4 -4.2414466801940726e+155 +7964e7c53f9acb9b 5.790333108190559e+276 +153b6cfefbbc78cc 2.1356090139414544e-206 +8d8936770036b33d -1.8462639553952832e-243 +19ab322657cbb11b 5.000300124820758e-185 +314d18789ed900b9 3.2935041297867784e-71 +8c0f7cd38043bcf8 -1.3743369133160846e-250 +2ed6a6a44b310581 4.6638816349639435e-83 +accdf531a0c227ca -7.18093671006043e-93 +3f18794d95f91105 0.00009336029096923902 +609b0d0956780267 2.321238974337054e+157 +a62be6b8cdde8ba3 -8.243574811914418e-125 +d6b333da667bbe74 -4.509800936606302e+109 +6e7f98c793865408 1.827426118770031e+224 +d499cb0882f55aa0 -3.525998354013164e+99 +b9e22104b5d81815 -7.15062104677989e-30 +9998a1bed37d0d65 -2.2644312886633287e-185 +c6f9854ecc4a0e3f -8.281951560112642e+33 +0f40f953e950f5e3 3.336537203358068e-235 +b2a859779d934cc8 -1.156060752849117e-64 +f7d55854cf405851 -1.7619484469295392e+269 +a01905ba6480eca1 -4.665635036314763e-154 +aadf4e75e796e538 -3.4944257254607533e-102 +e153bbed38a386f2 -6.936090843021099e+160 +1683351ac0133d7f 3.136623301118775e-200 +2b450025df79a4c5 3.000424468003783e-100 +fb2dc4d0f3aec6cc -2.213336440042701e+285 +2910775331a3a841 6.846879753563206e-111 +ed3ef1003480df51 -1.7066205466731166e+218 +a786d4175a6a2aaf -2.8289725526576036e-118 +13b76127c175b53a 1.0851219421048478e-213 +821a7441c49d50d0 -1.5800752464632642e-298 +1d9cfcff76b85e71 4.915919311005904e-166 +ab1f4f150896928d -5.591514801998784e-101 +69cfbf3eaca2bce8 4.8601591819713784e+201 +ebeb67ec29ebc391 -7.207950371457084e+211 +183129615396d056 3.7615045922028415e-192 +4f4c640bfe632876 1.0032443159600099e+74 +c1aaa946731733a6 -223650617.54531592 +527edb78b4cb3441 2.4553692103941066e+89 +54f7cdb535359269 2.0825729525883358e+101 +7f90a929266c430d 2.9249001258632043e+306 +a79e285c96c7f8cb -7.474452551293501e-118 +41b253eb2af925fa 307489578.9732357 +5a6a646cb438e931 3.573099860816482e+127 +6cd6c197970af4a3 1.961178010111922e+216 +43adb30b7719a98a 1070033427045336300 +7c80610ae21eb859 5.107814493819395e+291 +8e2731ccc1adb369 -1.7392381810645362e-240 +419b9ef4a1b1124f 115850536.4229214 +338f8d2901a903ab 2.4543110943223444e-60 +3322cb79af47db6c 2.2843844980431916e-62 +a041886869360fda -2.6153183692291357e-153 +a8e6777d9f16d545 -1.1677559117352182e-111 +c8e9217c686b09af -1.7513614662749862e+43 +af4cf7b143b4547c -7.634540779454356e-81 +6529272b0101a3d4 2.038530563407144e+179 +fe4599a68837d593 -1.808187293289473e+300 +1989cef04f90bef8 1.1862958114265724e-185 +f10fd1721ff52185 -4.0467046318598185e+236 +a86494cdb7be0a86 -4.178724078377209e-114 +8a74dc782c251713 -2.7135835419225704e-258 +4d46c1fa8420f9fd 1.872397324606314e+64 +50ebae0ea8b5674e 6.564074998382486e+81 +7abc474608541e00 1.642596770573848e+283 +ad300a467efb363c -4.921408596318897e-91 +c504600bb4accf50 -3.0790099384801807e+24 +70f13e8b948042ce 1.0965858153832e+236 +4e895b43c87631cb 2.1875519025646963e+70 +a539c8ae0a83cf68 -2.3248266563003445e-129 +3ecf4efdaa6412f6 0.0000037322709715035836 +62324e9304cca753 1.0542246865677955e+165 +f71ae45b90bc8add -5.419505404345578e+265 +c00786a950f4e488 -2.9407526325927087 +236e4e14fd7f2f41 5.0896321935241e-138 +fb4955542c18215f -7.534213733909116e+285 +fead1bb1cbd9a6dd -1.5594833618804871e+302 +747e41776f5c02d0 1.386385450797438e+253 +a6a3a075cb720ed5 -1.4845034559280198e-122 +555b385242adff88 1.5241465879456634e+103 +97fa37ab7e484677 -3.5914796935492175e-193 +5d082205e41dab3b 1.4369364367127032e+140 +468cb40b20823ead 7.277149772382655e+31 +46db688932b93190 2.2236289958690687e+33 +9f1a8b07e74427f3 -7.551857162538312e-159 +c9e7f9fadb85337c -1.095052208430067e+48 +112bc1eda525661a 5.858603690750304e-226 +da836e484f59ea56 -1.0522493155721402e+128 +50fc9a822cf30c02 1.3566216919754623e+82 +cf6f6d7120ddaa9a -4.4422079436068064e+74 +b5744ab3ae3bf74f -3.3897011370215456e-51 +ca4e09ed5fb1b361 -8.780344729922426e+49 +81dbfb430f802847 -1.0445643394784829e-299 +044224bddf33d9d7 3.723537731840538e-288 +289e12e744092ba4 4.884842248590095e-113 +143224ec0c5950f3 2.155864785371931e-211 +a4552a3a952fbe92 -1.164767681990825e-133 +b9cce41e56e6056f -2.84888122346427e-30 +443b9651f62d72a5 508893799244018000000 +a116aa5732c92100 -2.7696533358451888e-149 +9d98e11fec40f342 -4.21912302094312e-166 +55e5d1371984e224 6.254728226178562e+105 +86dba1f44f0be2e0 -1.2470568919650103e-275 +378ad027837b4d25 3.8474915798177976e-41 +1545ca1cdef452ff 3.393441009991886e-206 +179dfd641b36e59a 6.41913507768018e-195 +909c98eb9fa8aed1 -1.1788784364668368e-228 +d217860460fe6bcc -2.924696324587779e+87 +6a8d91b652eae41b 1.8541568388195303e+205 +83b39db676535713 -7.862779525455958e-291 +425090ca4e72557d 284594616777.33575 +c585531e91fa4e97 -8.249588016356446e+26 +f77e7b12640cffca -3.9313508784137977e+267 +2795b7f8756854b5 5.382874241450345e-118 +4807c625c7b9095c 1.0112347185237977e+39 +41fc7e901bc3ac4e 7648903612.229567 +8d955c3e4fbaf896 -3.128321537291561e-243 +479bb5f0534768e7 9.208458998482538e+36 +d888eaa9f0cb3ff6 -3.1416518971631786e+118 +1a65832ede099409 1.6201013054759594e-181 +1be83efd7993a561 3.063469840818368e-174 +e1d6d8a767f3fa6b -2.055671081636728e+163 +5ad4e52e6431e75f 3.6209747673201664e+129 +500bdb2c6ec9b351 4.031901786435068e+77 +3f89ee7d1afcd477 0.012661912332627019 +81334b5d654cd01f -7.033888994631825e-303 +45fe7b584a4fae7f 1.5093864175206675e+29 +3a6b610b01394ee3 2.7645799978963857e-27 +7cba7447b9c8e4be 6.599799691363866e+292 +fea656536dfb9af7 -1.1967256674381365e+302 +f3336eaba69c3002 -8.491820740171026e+246 +334277f6411948e2 8.978936562710827e-62 +f0124300c73302f3 -7.087916797310501e+231 +c7a06b161a4fb836 -1.09118359970187e+37 +91498828871feac6 -2.155538845091502e-225 +a2f9d89da595a493 -3.3912357520017954e-140 +e93726bcd911cb1a -6.922339521063601e+198 +f430c78101aa6e0c -4.805411108611266e+251 +21ef2f9d575339d0 3.1218476282414403e-145 +bd6f006e094feba3 -8.811207130135759e-13 +5452f36282bbfcb4 1.61913985230592e+98 +b6cdf3ddda4d264d -1.0493134480872821e-44 +40ad88f648fb8f41 3780.481025563466 +be85f1a9fb5f881f -1.6349554038316096e-7 +5e0eb3ce2fd6d0cf 1.19806328944061e+145 +b8d6a091c178aeae -6.80913255753316e-35 +4f024387c1e23473 4.033665522796692e+72 +bae5be09e9d48cdb -5.620262843495546e-25 +9954f74bf8bb7302 -1.2046429415897582e-186 +e12aaca1253e8564 -1.1719289909700426e+160 +5ffa05e4baa5216e 2.1806979525248554e+154 +2052e2fe9fedf0ac 5.634537088730807e-153 +2a3764162721804d 2.549703198277609e-105 +505f2dbf131c700d 1.4440985776929125e+79 +8805560cf37debad -5.048352259309239e-270 +a16650db3ee05b3a -8.726206420930838e-148 +f179bab111f4150c -4.188552397583857e+238 +48dedf58f03f3e26 1.075747906109507e+43 +c7a8f975ba6009da -1.659837081234874e+37 +5c7cd2fe36bfff49 3.3520861721026823e+137 +63ac67897f2a72f7 1.3721253886719669e+172 +0b7a20d01eb27bd2 2.2273719529800504e-253 +897e59970a21aba5 -6.023957992676611e-263 +018d13f5da4fa1b2 3.392176456943143e-301 +3d6ee3a772c71271 8.779259647170935e-13 +9310e6b42f822015 -7.66055676987219e-217 +51dc292de17d8115 2.1882887832778483e+86 +1900cdd36b539f57 3.017210281573686e-188 +ab4b00e871da0da9 -3.8580891926318503e-100 +8f5d8ed171ebf3f2 -1.162028345622514e-234 +61a100b8e6a09895 1.9123556894766845e+162 +a19d28e695915ce4 -9.12191866343953e-147 +f7c8d74399cbc75d -1.0252576989277322e+269 +269eccf65bc42793 1.1648270186626139e-122 +f1fd829194b02b1c -1.2298333242046797e+241 +9fe3d28786640c4a -4.620064608625435e-155 +6c6767cabc4696d2 1.5758819936897007e+214 +99dad8a134bfef7f -3.9488045850730246e-184 +04de2a4ffa00af61 3.1696515471762503e-285 +355f1ac35ccd567f 1.2989863465277318e-51 +28dbf86525c8b313 7.269066526306001e-112 +b3b3e5fe86a786b5 -1.2382814060280273e-59 +9412687bab6dc4f8 -5.468055327354995e-212 +96f758867d372171 -4.879908313090831e-198 +cd01e858becd3373 -9.208451322845898e+62 +2516df5a313815d5 5.155786772762843e-130 +3b9ff184a899b0be 1.6910711279712725e-21 +b0995938690d6c5f -1.4010500189855953e-74 +cbeac6aef5818147 -5.252385898036819e+57 +5e44966a9f283385 1.2853839875372647e+146 +6119dad0c32e22a2 5.679603625959747e+159 +d0979fd941ba9667 -1.7507325790975812e+80 +fe29f020fa469a8b -5.428280015779804e+299 +a8c0463d53b9d57e -2.114734736114678e-112 +6662181a8f6c3954 1.5376789305016105e+185 +33e247c88e1a3426 9.100738659132439e-59 +baa2deb84c93c9ce -3.0486128702070485e-26 +99bfff06da7b5ddd -1.1765788872046043e-184 +5291df47daef7c26 5.688483493672748e+89 +c50fd450d7bd6b5e -4.8099166734559133e+24 +b58ead7c38330608 -1.0249276455442163e-50 +b0bc30a9684be204 -6.232430725812768e-74 +a9f2e7ddbda374c0 -1.2879972377346597e-106 +717143f304cf0229 2.810696189163087e+238 +23e55faa5d99366d 9.18949271714249e-136 +e162a77537203f41 -1.311302081141656e+161 +87c33cc10c578f7f -2.8448442860938073e-271 +b7797ff755011fa1 -1.829525785657423e-41 +ad4d2a4c73a4d5de -1.7896854424585674e-90 +52f59ceacf272bf5 4.402637280529285e+91 +c9a2789e47c938e2 -5.272585573367412e+46 +21b38f2e0a32d213 2.447458525011276e-146 +951fe354263e5377 -6.207694144286993e-207 +731e1a4cefaa1211 3.2886862829697853e+246 +e05a95d11a59e275 -1.4257981964460121e+156 +5c6b5341cbb96cf1 1.588882401494871e+137 +efaa7b348f3b0268 -8.029814831550342e+229 +4781e2579470ee6c 2.971513918590821e+36 +76ccf3a413ce14b0 1.8233104462647924e+264 +eb15d4cb23c8a499 -7.00898490270756e+207 +02890fb934153390 1.91601961061405e-296 +89eb1fee1eadfdf7 -6.891268106847667e-261 +08c55d21a1c93bcc 2.0704857054463925e-266 +f5b2c4b102cbdabb -9.017825477491947e+258 +f63dfffeaef383ce -3.6900922006010067e+261 +15b8901a31a55749 4.896464195019161e-204 +44b12d771934efa7 8.111891564086973e+22 +8247be5e38b98fb8 -1.13454395275723e-297 +b6105703be1212a7 -2.7950534638638323e-48 +3c893ffc813a7f42 4.380167525121014e-17 +9e48b01b4f379b3c -8.574262948676516e-163 +15c7c01f313cbb0a 9.469024961882075e-204 +b0f6005656e940fc -7.78269023489559e-73 +1621e30832f12d7d 4.564010490356577e-202 +e32d127cdbbe3067 -5.4858728579509364e+169 +d9d25b0a6007b5c7 -4.853638732018212e+124 +71df7600d54a2b6c 3.2778448451998008e+240 +3ac0d47f0b73e43a 1.0876173652843823e-25 +76f9014dcfff4d72 1.259808939539982e+265 +65fb9df81ccdaf68 1.8335592014602255e+183 +38d2c3b8b61eee36 5.646747520196147e-35 +5dc7a4cea1db9e6a 5.7664007472241575e+143 +3024bf71de1ef3d6 8.959088383870142e-77 +85f3d9729306fbb1 -5.467534392635742e-280 +9f28e1d41ce43a06 -1.4158592177461876e-158 +0f0c53ab6d77b3f2 3.480097873014796e-236 +1c80ec065015e015 2.189391515208692e-171 +4d93edd638e56e95 5.24693482152173e+65 +c17778a4420da108 -24611396.1283274 +40374c85cd54f84a 23.298916657681083 +dd7ea3370a8d033a -2.3350281782982613e+142 +eccebd9d48c797bc -1.3246477181933098e+216 +e6528c245e627793 -7.880995769114605e+184 +e1762a85afc937bc -3.1163557451048696e+161 +80ea321be58acad3 -2.984317552861535e-304 +ff4bcf65fea50b86 -1.5256993363221554e+305 +fb0a4484fd486011 -4.882555753272685e+284 +6fcabbca038ad491 3.2425251536061336e+230 +005ac319a55ac578 5.95476715277705e-307 +5032b441c5ea2ef2 2.165790130033492e+78 +f57d0b8d756a062f -8.722271261268334e+257 +2c7057e30602b9e3 1.2242252469539748e-94 +785b00745f3ca450 5.705956172478884e+271 +596cf8c77e11c918 5.984992924714502e+122 +39ae645214a8ac8a 7.492176236939003e-31 +29db70fdb19d9f53 4.673770237190012e-107 +d9cc73a5bf2910ad -3.7616395469904756e+124 +3c878dc58f9ec9cc 4.085932250853847e-17 +4839365a34b4075f 8.579305860461765e+39 +4675ce439c155891 2.7642032958969555e+31 +7337ac6859a97660 1.0345186679512495e+247 +23904904b493bc8c 2.188042888251198e-137 +56b663219f6e38f5 5.25773671759147e+109 +733c75ed334879c4 1.24371618519318e+247 +5c3cb83304661837 2.0874465428551418e+136 +90b1dcd2cb6039c7 -2.945423591806982e-228 +0f16b13561286874 5.5757129595794e-236 +22d6e4d707b6a5a4 7.509688710648888e-141 +c7e3bd61d37a21ef -2.099092813987882e+38 +812e0cd216b14e6c -5.477470126718296e-303 +0d05489c58e237f0 6.088066215681906e-246 +7950d247011e0f9f 2.329582365966607e+276 +67de10d861ec3440 2.1433349224612699e+192 +8936cf89d4fafc28 -2.8297128663079672e-264 +a59f362ea1c903d0 -1.8011030517556981e-127 +ec7b0ab3651e65d7 -3.6414346812292054e+214 +f58e2d33114fccdc -1.812408285118414e+258 +0f41a58f158fe445 3.468783986201971e-235 +74e68e1a014f4acd 1.3229115234904107e+255 +af309a2201f6c718 -2.1877803569492897e-81 +da5df2e7398bec96 -2.0273008547741004e+127 +4b32c6dfd50f1ecf 1.7984652096845077e+54 +cfc37b706cdf5332 -1.7624084485667498e+76 +f9e08f6e939c6114 -1.174231428078735e+279 +66930ca41c2d9cd6 1.2950846708109278e+186 +077659ef8836326f 1.0329206094471927e-272 +b2a20676d77125cb -8.557999542532977e-65 +f9cb817e68afd540 -4.875855668950538e+278 +81110eb4f0f7daea -1.554599224421562e-303 +9e90a0f0c003cfdf -1.8480764817636123e-161 +c0991496e78bdf80 -1605.1473676543392 +5c217971033d283f 6.350528282912976e+135 +fca05149f028bdaf -2.03544956374436e+292 +0c408136b07a6714 1.1526098059136078e-249 +2b71855422d5d6da 2.0026070703857777e-99 +75f5bd7192d08bf7 1.671306473423574e+260 +b3b49167bae93720 -1.2799491006836912e-59 +8c36849cb3a92d4e -7.862736696842777e-250 +ed84d2959c04ec94 -3.675206246904114e+219 +ec979afdab0f404d -1.2714781336258366e+215 +607148c2a09aed8d 3.707895984014527e+156 +d42b734a68fd1b16 -2.9316800306681196e+97 +cf647a38a948e4a0 -2.8944385547722605e+74 +133c04a12c655d69 5.079740885529827e-216 +7050c8e007e1ed93 1.042340056966906e+233 +ca8655c257372a88 -1.044564293725099e+51 +c60b951f36b8e6dd -2.73163923683444e+29 +f39e5740ba978050 -8.485626525896372e+248 +6020708318a8bb50 1.102088317203973e+155 +898e340b5f913e26 -1.1989695939073325e-262 +cd1c540c211155da -2.9133977082512663e+63 +900e44f4d45f41f1 -2.4371182011507214e-231 +a7a67bf0847da332 -1.114520242694927e-117 +df5a475e6793d4f4 -2.1505181387613438e+151 +47e862d5d98d4e5d 2.5931723504370687e+38 +338acd1b7fa46381 2.084803529071976e-60 +89a9a9cdfa3feb06 -4.074987425329916e-262 +d0a7100439f19550 -3.418192032430691e+80 +82ea268e3bb9227a -1.2795464053576259e-294 +ebc24da9f0b58ebe -1.2034779555889584e+211 +9983e2992ba68a23 -9.140323119000301e-186 +28df244c7ef92ff7 8.093292278289789e-112 +c046fda7fd61c068 -45.98168914101626 +d3d243c3e13619e8 -6.095809176344402e+95 +4da8bb04f7f90fdb 1.3022151612017127e+66 +311df6ecf7a66b04 4.239834042890362e-72 +5f4ffdaec71766d2 1.3089858649879674e+151 +03b6b633caa4bc9f 9.103625242254328e-291 +892909e3f7c13226 -1.5530465214775207e-264 +12ae91b815a2f9c2 1.0824739397114725e-218 +814c04354b6d8cb1 -2.0427126955468608e-302 +71cdae1efcde5be8 1.546154199135924e+240 +1f4a16fe07721d5f 5.938315430017216e-158 +a656351cf91a4ea5 -5.2490528393760044e-124 +e2e3de35295f7378 -2.3431587650514996e+168 +fbc6c494dd82139e -1.7334398500589844e+288 +cc275a06b6bc3039 -7.329038827394513e+58 +a0b8667c5ee86619 -4.658841964187478e-151 +a9db236431a51095 -4.6221427343102885e-107 +5818af3b8d78dff4 2.4315472963184083e+116 +0f375e7f193d534b 2.2968143125787323e-235 +7d75484734689b2d 2.1747786272477235e+296 +7efa3311f1f5215b 4.491677087033806e+303 +42832bdef0c7fdd9 2634893105407.731 +2fec02e9cba62462 7.55971870595258e-78 +1d3a3382546030aa 6.942625408625593e-168 +8004705f7a479a4b -6.173129311275477e-309 +0e40cc106962e7bf 5.038119095637995e-240 +9e91df42169e2db0 -1.98626857441528e-161 +79668fb60b587deb 6.249014462793078e+276 +75401399114cd7d0 6.034750126312003e+256 +d87a82909e5fba7f -1.6712767055993203e+118 +09cb7ffd2a547acb 1.746649594642572e-261 +00fd278ccbbb00fe 6.6427799841397136e-304 +ea481e614e0d897f -9.452364878761173e+203 +97c84277e5862dad -4.1541023506183606e-194 +f9ccef5d7b1feab6 -5.129201915191777e+278 +7992f8358a83b8e3 4.203329508567154e+277 +e5370d3ef8c0e752 -3.736467297819829e+179 +e39ca0474ddaf21c -6.914161954340963e+171 +b5a4b6136a4e40f8 -2.7678127339151183e-50 +0ef838c7d3e7e279 1.4878865400410562e-236 +0f8fb0d17e0d93fd 9.967016182345959e-234 +903b6ffb42ded79a -1.7672855361824246e-230 +e3603d944077ccb5 -4.903296042021434e+170 +c4f809d4d04aae6c -1.816290417990065e+24 +e9bd6be27eec2030 -2.2520644833716042e+201 +08a7ac486dfff470 5.735704773846914e-267 +05bc041a53671798 4.8231481392273755e-281 +e5e1890423993fb7 -5.821013274647202e+182 +cbb35ba3ea770008 -4.746560410229812e+56 +f0c708650872ec08 -1.8308518778519043e+235 +6d9b388994f80bd0 9.609007310656291e+219 +369a6f50a7270fc7 1.1575962143472132e-45 +4d00c7e78e625018 8.62906550229951e+62 +c9d8e994b69992b8 -5.688992275394886e+47 +8231bc3714fd0f9d -4.23721763274659e-298 +d4f7f8dc81e0b2c2 -2.0973210078910973e+101 +4bd91d1e50d98327 2.4631488280346258e+57 +2dfcf65359be19e1 3.639761729419591e-87 +52ec573028933d92 2.8865389089331685e+91 +54b651f8756d3f69 1.2204967000897398e+100 +0462bfa1478b1f57 1.539082653051826e-287 +27ccb22cfd7b3ca9 5.689748157340708e-117 +cf5b046f1cfbc090 -1.9094189336317043e+74 +3af6427fa2021311 1.1508026206760025e-24 +79fbfa12794ef377 3.9674797679823986e+279 +8a17668d76cc5351 -4.756096689675743e-260 +ed83ef7a2a313fb7 -3.518625700330424e+219 +779c98d0d5cc5008 1.4753520701121878e+268 +fb9495881822caa8 -1.9589706540369035e+287 +d2b56e4b138f253d -2.728461123536692e+90 +97c7f1af2aa8d437 -4.100066904896181e-194 +8886784f14f0685f -1.361056327066637e-267 +4cc9f27445567f4f 8.339072135882336e+61 +9a341b7ef748e071 -1.8928608354772464e-182 +fd50fb3c5f724df1 -4.338194253678942e+295 +df947bf058b5f52a -2.682103154971557e+152 +1845f3be035c9440 9.622971545318144e-192 +081a225c40273d68 1.2367258504572722e-269 +5ed1248644e12912 5.479947984023248e+148 +a531c5c38a80efc0 -1.6024731513289778e-129 +d347c7568c681e1f -1.5500158336176586e+93 +b2163d9f6b72a9e3 -2.062384810737992e-67 +43ef233474d25470 17949557725681910000 +db2c7ab4bb90ecd8 -1.5792749639834505e+131 +64e84e92dd45fb01 1.231225318930078e+178 +afc04cd3378f90b7 -1.0997686958486157e-78 +9bfb9e5204be4256 -6.979135712523591e-174 +931133a291c96b52 -7.796765705514218e-217 +e2bebcec51d26d04 -4.531405668932263e+167 +d43e102fa1f608de -6.421466273845982e+97 +31a5178164b2134f 1.528005889808094e-69 +b0b1804d81c444a9 -3.8692649824720696e-74 +019286926f83c660 4.322333905148811e-301 +c40bec3660b959ec -64385373522630640000 +cc3c2846a9add05f -1.7674641388940873e+59 +60b57f8b0dff7c3f 7.379044992299155e+157 +e2cb817051b26d07 -8.109779433086941e+167 +13616005c010a81d 2.5201135841212147e-215 +0870aec586a32e0d 5.052545661026924e-268 +81c8cf6556864b11 -4.630880214786634e-300 +c3e9c4c9b355a3c7 -14854645747652900000 +dc0a910073a31940 -2.4136872297013662e+135 +1ba33b1ee64e0f72 1.518633776289793e-175 +b00875c696370fec -2.640515168702533e-77 +3351280442c31af3 1.6681876632338336e-61 +104430a019d24006 2.600928523209528e-230 +b086591f2f7d2506 -6.176071543461437e-75 +9440221dd7525ecc -3.833852428202183e-211 +aeaf78a3e150c971 -8.10000374701401e-84 +a439a59cc2c814a3 -3.5285576254670045e-134 +c2d7be07d348cc4a -104419770049329.16 +e0b1444e09f00f52 -5.926659827495521e+157 +215d246e1f927bcc 5.697784852850555e-148 +087d96c7303c2c3b 8.9613577669864e-268 +cf92538141963aa3 -2.072292918592909e+75 +fa607cdb5de3de16 -2.992861547778115e+281 +3683319350395c2a 4.2025059341068935e-46 +22569fb59b6f6412 2.898870871990688e-143 +2940e2843bb6fe5a 5.616791326540461e-110 +bf0846538bf94526 -0.00004630033936038119 +d028477486433e2c -1.4056651174256946e+78 +6d506a98fee5b350 3.621885496999069e+218 +879ef3939872ac36 -5.72143832625032e-272 +ea66b543ffb384ee -3.559813741049805e+204 +92f69f40d4fe1867 -2.5633961688426032e-217 +3035eb6e92f31d97 1.8930184367565367e-76 +9ec7a8046c7b7e6c -2.1032914682565517e-160 +3c9080a332919390 5.725451431273814e-17 +f2a42af84b8c54b7 -1.721337891410918e+244 +dc8970ffd12481de -5.917375114571237e+137 +bcee48f409277f5d -3.362307509099303e-15 +259da641cac746e3 1.7109541159233746e-127 +f8391499310310ae -1.3249871185591675e+271 +16a725bddbaefd0f 1.5120133654907888e-199 +8c1f97fb926d0335 -2.7579339736299398e-250 +36e798a23689b473 3.3065354100946694e-44 +b7e7f5c051b1dbdb -2.200375347803991e-39 +c9de4c37db472eac -6.9187778220794255e+47 +0c3b07ee970d8bf1 9.438554080910414e-250 +dd5528dba1734ca6 -4.0316744798149906e+141 +f82686a0a40843bf -5.950151900188591e+270 +c6a8a94b56d75cf8 -2.500953531564658e+32 +520bfc7306b0cc41 1.7397691031906245e+87 +02e63d916e349d99 1.0882252709609636e-294 +b4301c8e37b8b2e2 -2.5667172125757125e-57 +85553e83a5a93b07 -5.714580958540066e-283 +a3859f3a7eae48b1 -1.4525382523364887e-137 +3adec4e058171860 3.9768062740225015e-25 +777cb6a722cd2e50 3.703412736913844e+267 +e4ddf6493a11200c -7.5883797050031505e+177 +abf2e8f00ab4814c -5.533130711188906e-97 +97ea9f25a1343b4e -1.8234256457278186e-193 +ab2c7777a16140b8 -1.016782622727276e-100 +29337a946616ac39 3.239846195700238e-110 +2d99085740b4af21 4.9154912651977805e-89 +83886a750a842e3f -1.2233367714090517e-291 +02bcce09571b49a3 1.7617715131727037e-295 +a40ccb8505b377f0 -4.9520955519843965e-135 +1acc88d2d7a5ed1f 1.3753155868760559e-179 +75c5322624d80105 2.0368452465391913e+259 +38864832fe3f5047 2.09539185192761e-36 +dfd6ebe54051df27 -4.8019785148408315e+153 +2c7e5dec0ff05559 2.274686710251723e-94 +51581909a637c3b3 7.314695813141767e+83 +edf45e6e811520f4 -4.60175868328197e+221 +9c5487d3d22bb7b5 -3.320348126937229e-172 +4c6cb344d2a0ad9a 1.4412360618486002e+60 +9935ec79721fca41 -3.149177307093129e-187 +4b8e4b3c756b4595 9.285050659208628e+55 +afd869de935e565e -3.294368035769866e-78 +5a221a8f49c2bd72 1.531851682562209e+126 +31e1f4f039402488 2.0814199328115695e-68 +171e176a802886c1 2.5159745287362316e-197 +979a3e43c0c9478c -5.617200962773713e-195 +145e590731eeb603 1.442347051755692e-210 +5a49678585be8baf 8.598383506688319e+126 +3f6cc459ca92fd78 0.0035115960823472424 +c34b36de8f138682 -15320307764497668 +293eca8a0422410f 5.121389012756232e-110 +5bbbf42d1c01a64d 7.936685159743255e+133 +7370ce315e1b9641 1.175022959204703e+248 +7a10192dead0112d 9.131824072372346e+279 +e7b9c177b3ba5a4f -4.590195362269035e+191 +ecf5eb6a43745d3b -7.556302331580039e+216 +75be10c7f7c09b41 1.4445928454297757e+259 +f10e95204f0fea37 -3.889555226345808e+236 +b01daba3016b9823 -6.405976481792163e-77 +3551c50f212d27d3 7.42099891757469e-52 +8f3a60d10264e95c -2.592557389840066e-235 +c29f48ff4b6df78e -8599595309949.889 +6a74003fcfb50fe1 6.270875915365589e+204 +d7d2e71187e9bdbe -1.1637512587793557e+115 +e6a0643eebb4c145 -2.228785563126993e+186 +078630cc50755987 2.0509889570827013e-272 +cb7278d11b5b32f4 -2.8308166780667036e+55 +937a98f27addb991 -7.715482755364509e-215 +fde922ee133046a2 -3.287831716788517e+298 +ca959eee968dceaf -2.022328039564404e+51 +c0f47eaa6ce29ef2 -83946.65158331001 +ad1fd7d51ddca74f -2.4425114496872667e-91 +2a4616c80fa52dc1 4.815566496604431e-105 +bb6b8f80bcfd37da -1.8238047491394092e-22 +9550e74bd2587b35 -5.26501521251822e-206 +54df57339182a683 6.8549817041671866e+100 +11d6d2f904c4630e 9.865909136032594e-223 +091476ec9b24e848 6.346670876702216e-265 +6a405a007111b398 6.408354372014524e+203 +c8d65352ee1276ff -7.779295839159925e+42 +efa275959a280dd2 -5.597347451830554e+229 +6ee89217e8376949 1.8189631003366998e+226 +0bcd75e7cca275db 8.036644486538846e-252 +7aeda5512afd27f0 1.3776175287234921e+284 +d1c62930d06121bf -8.610258635123529e+85 +f27ce82655c66c3c -3.084019082122535e+243 +ca1198397ab96fe4 -6.428644066266685e+48 +cce58dd6439d643b -2.7708819212057985e+62 +8bf8b2a6c4a23033 -5.3899272056560274e-251 +f95afda4d6871893 -3.737934933644474e+276 +d0d60046793a5262 -2.6086916906999312e+81 +f50090de0d9cfe46 -3.886521403607996e+255 +294bf52e6504963a 9.30022895518909e-110 +9cba31a549005196 -2.7112096229261124e-170 +d05316dda0def4b5 -8.8415686466778e+78 +d4ea56ea7a8cc41c -1.1522224812835694e+101 +701a5c1e20e35a94 1.0231030079749323e+232 +6d8a357f3d64b921 4.6259065600965457e+219 +199f835e3d321813 2.8970406494365713e-185 +b0c4b1e9bf0a18e3 -9.150734054763387e-74 +ccbdaed845ca5412 -4.7698721974644295e+61 +afcff180c522843a -2.1552213848026986e-78 +7cb1afebc3ea8fb2 4.41260115564076e+292 +d5ad30485362f62d -5.2300017161135534e+104 +97e3f92874be1881 -1.3680502412195547e-193 +1db6ebd2012164f0 1.5548168394990616e-165 +a40eb169f5961a39 -5.2785133130260715e-135 +e34ec9bdb026984d -2.3238589265273263e+170 +2081ae94dd93663d 4.220098402391726e-152 +33e06b2023546fb1 8.173785763914995e-59 +f1d1106a2da30b2e -1.7778718665105167e+240 +08900dcb7d2f46b8 1.94484072647968e-267 +686fa10d24b9b635 1.1544473726265924e+195 +ae54d526f669e819 -1.6755803265319007e-85 +e361d20427ee1d89 -5.38027432800611e+170 +f03b3b7b107383f2 -4.227871073272509e+232 +75f5e45c3a27d875 1.6829931593815118e+260 +f7517e88a5a12a85 -5.640947585242622e+266 +0ce099082ad06990 1.1869258447563515e-246 +212b1831d92bad43 6.6217604607732e-149 +b00c72e71068cad9 -3.071112029946262e-77 +5dbb8b88c7398d0c 3.3589350810379623e+143 +200154a353593d16 1.6157205973876136e-154 +23ebdc91f1ca0aec 1.1978930557796874e-135 +7a12d280aa04a5f9 1.0676972316541286e+280 +a49d3fc25b22f2f2 -2.5754541388770447e-132 +d05188cffb04fec0 -8.12139013702844e+78 +2e0a936ab46f473d 6.679721806344373e-87 +68462e0f7fd7f6f3 2.023891723241662e+194 +30301a6161b785de 1.3906864046572408e-76 +af6cb26784845d55 -3.0252831638778646e-80 +3db7c013a06ea0af 2.1600771542119094e-11 +7ad24e5124cfb62e 4.2533157774801655e+283 +8744dd41862a62c2 -1.2052506509037402e-273 +746ab3924ee8c687 6.117602371582106e+252 +b52b5a93650056ca -1.4279355904257875e-52 +577bd65240c8a4e7 2.6778350454364762e+113 +d705788e1a20ec6e -1.6136117098101818e+111 +5d9fb5f97fce7636 9.667310889854746e+142 +1c1fbbe1fedea75a 3.2076442058832674e-173 +6cd1fff147498c94 1.5512578396800813e+216 +42e504db332cba8d 184884812932564.4 +df4d2c101845e0b8 -1.193646819290363e+151 +18b79b721fe84579 1.3246081003277963e-189 +3c7279edde0febb3 1.6025623379491305e-17 +c8f9abd68a7778a4 -3.5780489708619456e+43 +04a2d73f2b033f55 2.4746492234264024e-286 +2e8e5cdb6aa64c6b 1.9536733172289104e-84 +d3066336480b7a33 -9.120847739841644e+91 +c998f964490d8007 -3.564435003533924e+46 +e653fd5b87717ac7 -8.493821905952819e+184 +6196fa5d509dc9f2 1.2921997482753996e+162 +6a7c020fe9b2cee1 8.781324462639433e+204 +e26602dba576333c -1.0140296142891511e+166 +3b4a1f36a963905a 4.321510451204158e-23 +5f404e32ed2a01fa 6.671769324867285e+150 +694a1b2e54bd6b79 1.561170757656767e+199 +364e9a5955ffefef 4.1878742899119314e-47 +93c1712372b20bf0 -1.6190790339466317e-213 +1609661cfe6593e7 1.6201957606127806e-202 +e2f8b5216e814100 -5.827833297823683e+168 +b6356998a3280382 -1.465101698409042e-47 +f93cc13bbe14f305 -9.955582288243552e+275 +62f025fda572bba3 3.808966300196461e+168 +7a61a5b4f9cad614 3.203347493250458e+281 +67bbe464353e46b8 4.970949084448579e+191 +030df82352789435 5.865576243378769e-294 +bfe0b1b708c5c85d -0.5216937228526707 +5098215507b7f48d 1.7882154886214346e+80 +9d5a99088e9c1824 -2.8190833746070822e-167 +307208cde9aba914 2.49196878279259e-75 +b4afb9f31b3c3b46 -6.469506132536067e-55 +b15593f9100c52b0 -4.885090413352008e-71 +ca1a3246a744e615 -9.571516933126337e+48 +0b8c8c81f2c34a99 4.86746002518175e-253 +8c50daf2bc3f4a4c -2.354177575418857e-249 +b6ffefd7f06c27d8 -8.950622623714337e-44 +7c609b05d3500997 1.2946109882241715e+291 +2d843aa1554e2c44 1.9861234150646404e-89 +cbd4b3e6c3df211c -2.0305188256943608e+57 +2b87002cad0f585e 5.257897835232613e-99 +eb5929b77e54916e -1.2925836813673888e+209 +fc1136b25039a3cc -4.193812646727855e+289 +3feaef5d499ea38b 0.8417192876705583 +eb01c54a78cc1c0c -2.852664997550042e+207 +6481623152d30734 1.3758349246448455e+176 +c5a10e583c25ec3a -2.6392934031126154e+27 +5e3cac8cea65e962 8.951309660508058e+145 +1d7dc46da3873a30 1.2620071788428607e-166 +85416c6c0ec0b844 -2.3434197323809606e-283 +414397982193a834 2567984.2623186354 +d6cdf2f504f40264 -1.4067324410694495e+110 +81dcd3acb571f360 -1.0761221658270639e-299 +348f0b9f1f07c886 1.5826614539382628e-55 +f709221954f52697 -2.5325263212952803e+265 +ebc16319edf3891a -1.1432323086766053e+211 +f218bdc802f96888 -4.124389169172017e+241 +19b2fd837105b859 6.98319543692803e-185 +6b8795818b508569 9.691803660537452e+209 +0f415236f08c5e23 3.404788490092025e-235 +2333e8426900ae5f 4.179204023445718e-139 +fad6a24d09b4d8c3 -5.25892544846465e+283 +5438f66ea6ede1b2 5.331984534030275e+97 +db8c312a510c16f1 -1.0005407751251866e+133 +0ee9a116e14d929c 7.871715865518974e-237 +ed34b0b570ca2eb9 -1.1412032662708994e+218 +cef6807f043f34a4 -2.484842707374427e+72 +3ab6b4e4d4f4e3cd 7.33690355801612e-26 +19bf021e0e88594a 1.1402503750366437e-184 +8c9e0d1c2d5a5b78 -6.715611471139784e-248 +aaebe2546b6531ce -6.224838954734295e-102 +8cc1546a2c9c482d -3.098173558478903e-247 +b4b4247fe128dbfd -8.214777690066059e-55 +6f8f30ade5b7d50a 2.3644178492476098e+229 +fa8feb525968b820 -2.3175990100810886e+282 +d1950adf72506150 -1.0219638953947099e+85 +8297d0517fa29592 -3.6412621609097575e-296 +f0d80acf8f38f439 -3.822182035728087e+235 +01df79e797beab91 1.1750233218424142e-299 +7732889447451286 1.4940114322298686e+266 +a69479c3ee99f923 -7.743537197422686e-123 +91cebc25343ce211 -6.642715145092793e-223 +5be954cb92043f95 5.753662436707183e+134 +fc1ec17ce68972aa -7.4931261601029465e+289 +5a54198d86d38acf 1.360599366656099e+127 +1cddd2cfa4517e1a 1.2347549764324424e-169 +fbdb2f0334491a66 -4.139267711375867e+288 +e135394ea08fd1d2 -1.8649277062066903e+160 +ef9b1c8411e2faf1 -4.11044178063762e+229 +9e19f5c678a9a344 -1.1270107382023793e-163 +50adac17a85ca102 4.3978370525588977e+80 +ebb90fa6358418c0 -8.239059319623456e+210 +6ed06566da1320f1 6.069002841510474e+225 +ec09a9bac2911af0 -2.699817982135771e+212 +c09aec12398684c5 -1723.017797567228 +a8cbc3ec9dd4188c -3.6078996829974615e-112 +a8fa5906958213bd -2.738957274487198e-111 +9f5e4a150bee58da -1.3788363267077171e-157 +f3ccf057f4a2f2eb -6.4748181117401195e+249 +68a1d98af6366dce 1.0424133881611633e+196 +1fb2e1b3c6d2b295 5.501006834820377e-156 +63b5171966c112b0 2.037599759984234e+172 +efb91b8abf343c95 -1.5226555195675549e+230 +74b81d5b438fd1ac 1.7679816364828856e+254 +d07c8a52a6d7250f -5.2875899275181454e+79 +27de16d103f82885 1.193194590838665e-116 +99488b09e9cd5994 -7.050866183669565e-187 +4381a1a86e342627 158810220146246880 +fd37ab7c787327ab -1.5117208548515677e+295 +203d65446ef74b24 2.192422177833997e-153 +0b1ba6726bf8ecb2 3.6829998610885136e-255 +fe9c99df8f5471eb -7.661572501068163e+301 +20b6f032ecfbc3c8 4.379686045074267e-151 +66a053a14335c64f 2.219960252689992e+186 +ca6d3b27c80dde03 -3.4177012075450796e+50 +ce97aff675d4257f -4.087102798464983e+70 +5c0362bc0d7332f5 1.7612830426330783e+135 +a59cd523dc5bc9d0 -1.6638161265669369e-127 +80ad37a3e0ad0a43 -2.0803439702754395e-305 +d457a3ec6c48a097 -2.019817218226697e+98 +d4191f6c5a86f416 -1.3415465525440941e+97 +0c32870c85f03c7e 6.469358915258192e-250 +3f517f6be0770386 0.0010679772839099224 +e65aef6c5205cc01 -1.1445066247083441e+185 +ad0d6f437de0e7d9 -1.1288852994380163e-91 +9519f54d7f75f056 -5.053331587261691e-207 +deca37ed1a91ce36 -4.190589168612505e+148 +6e2b41b51d45a02a 4.926285829346533e+222 +b5a2fdfd51c661ea -2.5380769597746773e-50 +311d40ebb992d7a9 4.139237268980559e-72 +f75e96f4202d3846 -9.863495752567005e+266 +0063da830347b3b6 8.835128181494052e-307 +c0f9982800791151 -104834.5001154591 +3035b93842719733 1.8760793458022017e-76 +ae35acf92dccd8dd -4.358468613865418e-86 +a339b3279be2d62c -5.395257118833341e-139 +61c1179147a51880 7.689570696209255e+162 +f18307e3ca1e72b1 -6.196180633685849e+238 +90e708dcc068c214 -3.0386053934832474e-227 +a2ff9ec456ba1690 -4.1488373930837966e-140 +e5f33ebabf3ac6bd -1.2777212926698605e+183 +c03e99ca24c2bc70 -30.600740716498365 +b6f07dbc9dba2508 -4.621807241417808e-44 +7b9afc85e82d1342 2.5682726559027146e+287 +8e44c0a0f919d9e4 -6.224478504874108e-240 +ea05f57ee30f1357 -5.3787209435975655e+202 +ae9903834e0538b1 -3.218989401879854e-84 +feda651513c13e80 -1.13129248108414e+303 +dedbea11d7831cfd -8.92329308396046e+148 +645e0da97c7c8584 2.9732445703905985e+175 +4c65b6499a0b9d8f 1.0903105048339483e+60 +435c2cd0ea810974 31722300588172750 +bc8c299266f4d666 -4.8853959367313743e-17 +0ac1d206a954664a 7.417738178253692e-257 +3aa4fe8e45da1c06 3.391821533403063e-26 +1c8d04f5bf2a69be 3.754572781974891e-171 +95863385f62392ed -5.532107872163632e-205 +93e76eb69a66ce88 -8.700640297592099e-213 +c3ceb0e0f51aa115 -4423029520714574300 +03c747ebc4547117 1.8663570351900998e-290 +7cd35adb70887c35 1.9314655663176104e+293 +5ee0f34268eac18d 1.0836861426597696e+149 +1e4eb2562026d74e 1.0661124299284965e-162 +34e8c0cb4dfa8960 8.07607641327425e-54 +b8e5a3f8bc0b2472 -1.3024410684444646e-34 +0ea1d779f6406eba 3.424914912624099e-238 +dce344a299ae00e7 -2.868184196214682e+139 +466f17bb7d4d8526 1.9707342832177259e+31 +34149807c72a97ac 8.201981404359045e-58 +361ce9ca03f2a983 4.945810189701042e-48 +6fbd9e39a64f4c10 1.7961947316716087e+230 +628901000242d688 4.6076075766543965e+166 +e4b9c30f2a68f125 -1.6311586497843164e+177 +f17cf647232ac987 -4.7148239055260236e+238 +9b6707da05461dd4 -1.1366823996880503e-176 +8493391eeaaf64ef -1.2624325811241849e-286 +e970a5fe53f2e1e6 -7.964709729290664e+199 +1e8aeefc749bbda5 1.4966690236380996e-161 +d68c70fd5a15439e -8.349469849317361e+108 +a033e70c10b04e99 -1.4843983411893903e-153 +5ecb9b737dd9a844 4.412561069565436e+148 +53bd0fa0cc688a14 2.4247664206402482e+95 +fbae89213a905e00 -5.812102094082194e+287 +5c80154a2a4afebc 3.74075699850159e+137 +5bd3e44ffbf44441 2.259088059291707e+134 +791a8c3228889c89 2.2978516114372596e+275 +c9af0a6e628f8ff0 -8.860566861873788e+46 +2fd4db1d35896cef 2.814299883213201e-78 +9370931e0bc8a5f6 -4.808041239849587e-215 +6996d652c332893d 4.3701867063705284e+200 +dc6bc14afdef036f -1.6138756419486027e+137 +929ffcb93eebe6a9 -5.6634283849328704e-219 +f877dfa7fcf2bb24 -2.0179715927842995e+272 +4f7797fbf6651752 6.669830253326701e+74 +4d398ca9a9de49fc 1.0510439804806076e+64 +bd9768a150fe0a6f -5.322524769453393e-12 +d8559d1af7e68dbb -3.406491423365853e+117 +37e2fa5b76dd4e60 1.7428502013638888e-39 +e72b7af707576cfc -9.565535145331977e+188 +6b36d3132b577d25 2.931153102131931e+208 +8e1c71e142443a9f -1.0664660264932251e-240 +321534217cc9f52a 1.9662164246667938e-67 +e6a65c5fea686c40 -3.040432872636833e+186 +d9d67e0f9b1fac98 -5.947500885565712e+124 +52d6ced5ddd3b5c1 1.1615154103918717e+91 +511085fff1e942ea 3.1347232805187356e+82 +0b469d3b89d96aef 2.4097650662063735e-254 +86e16443a23983fa -1.569766070909634e-275 +2f74f4e007984e7d 4.418560579095395e-80 +fb7d756dab0941a1 -7.008894707232919e+286 +966255faacff1b62 -7.485728928783528e-201 +a9afb07d7ea47dd4 -6.746612799263951e-108 +e0321a6f22a2c02f -2.4272501005896e+155 +a1b71963d08c6e6f -2.8904047743934176e-146 +e9772642a31d0d73 -1.1074851106723627e+200 +142e8da957e40ba9 1.815148279668138e-211 +e4266a636ec009fe -2.772027165865786e+174 +8e50e1f475fa366d -1.0127534423611254e-239 +f461f93657d3f941 -4.117927385253268e+252 +b186dcffed4c2ef3 -4.1408505518826795e-70 +4ce49b92109f6a6e 2.6492234524710604e+62 +364c0860e05d0f3a 3.836154199327873e-47 +9ea9ba31e0713ba4 -5.7185633540419634e-161 +1adb2824d92db0d3 2.617829653650466e-179 +67eed7b4130dbf72 4.397421627032981e+192 +769e7a2a7eb2388c 2.399227597376404e+263 +bf8700363e2afffd -0.011230872890678251 +d7c7ca516d92f542 -7.323326130857388e+114 +d6891695750cc028 -7.365093548015491e+108 +eb3a06e32383d3a8 -3.3424084174913233e+208 +f61d50dc035b3e0f -9.014857350419438e+260 +e692c9b1dd35ebb3 -1.277305816195105e+186 +c93d26f557cb0ca4 -6.501153634998189e+44 +fb101772494933bd -5.982115718848458e+284 +bdc2112604d4ee9a -3.286365676463912e-11 +c626dc74a3bba1c7 -9.056236430382609e+29 +2cb4b8cb5ea44744 2.4835330569227054e-93 +aba95b7954745eca -2.3186477718394066e-98 +7bef9d30b9fc44f7 9.627768700803814e+288 +bb6f471bec7c61be -2.0697891847776867e-22 +16e9b3f7257136fd 2.6863123686815923e-198 +d3391aa42609cbd0 -8.182062089018656e+92 +7b3eefc38dc32c47 4.600321407615629e+285 +81052c71a8255bdf -9.648710546797028e-304 +3a4dddf7e40806a8 7.539506865945261e-28 +0e98d51d705ebca5 2.3834362762227684e-238 +0fd218eb9bbd889c 1.8213701892617753e-232 +a6c0d0bdbbe1548d -5.087434620834854e-122 +2fb2edaa389a1564 6.385494852289987e-79 +393bb469f85ef84e 5.3357392174863355e-33 +83c02047cf65963e -1.292776433769641e-290 +16f71eaa0e7d1292 4.83266402422945e-198 +c37ed5357f190c37 -138858800698409840 +8dac2e6655fcd3ef -8.254556517482564e-243 +4250dd2c1f0e3188 289720532024.7739 +31a6316add5b4feb 1.6077841578347454e-69 +0e4c138af0a763b4 8.421199170936064e-240 +e32f76d5a00e4073 -5.9372348908530846e+169 +5f4da8f180b62033 1.2136069098828093e+151 +942a2ba4cb5f20b3 -1.5547651597743325e-211 +cfa96d851573d232 -5.750663027631445e+75 +1ab78f62cbc7f916 5.677795604460004e-180 +29e160afe644f364 5.919478451247335e-107 +ef4018eff1dc2382 -7.626807321331859e+227 +57b6e797abcd3b45 3.5253495277152757e+114 +bfc53bb2705ad073 -0.16588430868216938 +3e26a3526a261913 2.635407296203291e-9 +040d7f66679471e1 3.783569247463338e-289 +4b51f005c2ff0142 6.872318374027254e+54 +a02ec7ef4912efc0 -1.147875742141012e-153 +69039516fafd3a1f 7.319015799703559e+197 +0a2d96d68053a7ab 1.2027825772345521e-259 +e92432ce59078a24 -3.019711427844221e+198 +6b99e83a6d738c30 2.12929820761332e+210 +678df147fa9c6728 6.670454052987395e+190 +5006f923755995e6 3.325143140106251e+77 +2b727c7269f09f4d 2.1129403006940363e-99 +f6680cf4f88b0d33 -2.3666410328122915e+262 +0b9ddafc3dcd37e9 1.0180443803593805e-252 +025df9f3ff8a77c6 2.864728502049457e-297 +3e04292e114252a9 5.867582275246339e-10 +ebf70792ef395c4c -1.2113875102264794e+212 +aa910eb254d1bdf4 -1.1899660022398892e-103 +1f11adf3f4b7e38f 5.030050762858319e-159 +337d096cddc78f88 1.1293534900912622e-60 +f4973b2c4140e297 -4.258012810755396e+253 +bf11fd32188c2692 -0.00006862276326216114 +230967926fd8da5f 6.666594098098232e-140 +b79f90c99e2c1c2b -9.05887616964059e-41 +1999a56e58342ed3 2.357685963856359e-185 +20061e1f28c9924e 2.0620133310704283e-154 +03e25d9ab36152ea 5.889254111618102e-290 +1b0341fda1906acf 1.4851103293523897e-178 +6b92eb04002419da 1.5548657453699294e+210 +29328be44ccb6769 3.084767546912167e-110 +444d2ab5cda80be7 1.076066340359185e+21 +a8943ebc492d4230 -3.288370308961243e-113 +4ef095a5fbc194b4 1.8313992591466115e+72 +19ec470307ffba9d 8.318633288797824e-184 +ef482d298c1d42a8 -1.1454566047154588e+228 +661e2692d45ad22d 8.007085281699931e+183 +6a4a9ee724914cc9 1.0432941902818903e+204 +94e698a51f327c10 -5.498569918677206e-208 +f9167317efc11ce8 -1.943141162937962e+275 +f8fe407b5a66a4d1 -6.5461878509789805e+274 +a2530aa31a517dd8 -2.43985714344408e-143 +0cf4a839b5c7a923 2.95442895163601e-246 +6bb8fd785f000eb1 8.215713543930831e+210 +c61505a26bb382ec -4.1638379810901254e+29 +72e76df60e0bdee9 3.1995593222607283e+245 +a0b92915bd155314 -4.803980598534536e-151 +d7845b38003d3cb2 -3.9164061283509256e+113 +67492c418c722a4b 3.504936164571827e+189 +7dac32a62f173cdf 2.3051633545647543e+297 +f75b8491cbd1c566 -8.873003839635159e+266 +dbe667a7da43ff6c -5.088985172422113e+134 +64904c1c3dc12392 2.5797242994185584e+176 +b5af4f335b2a0555 -4.1841299480849135e-50 +30ccd971ff89a41f 1.2756390102451248e-73 +ec2eb6756bb25097 -1.2924250358918716e+213 +8cff94f2c40ff1f6 -4.516903762623379e-246 +614ab7617b6f5395 4.695096524198897e+160 +9db0c54318dbc872 -1.1376006474849943e-165 +aa3b353897d22362 -2.9657631578371594e-105 +f21fb3bf69511fa4 -5.2847580861589e+241 +18304e49c906d49b 3.573923419571782e-192 +1ad506e2fe569ff2 2.0269258709394035e-179 +2c49b07d61698e4d 2.405390362660535e-95 +3aa3706a2c236e11 3.1405592267159725e-26 +870c534084788c8d -1.0226499505399424e-274 +1bee9ecdf76afed4 3.8688544398304885e-174 +55669da2d5b31e29 2.532677139510326e+103 +42ea5caf3bb0d255 231883033904786.66 +803063dd9edb86b1 -9.117295990722246e-308 +021e80059829bdfc 1.8217356819880036e-298 +cd7752f48dc71187 -1.5351928341482428e+65 +06f27e9acbe01e64 3.338625675137435e-275 +82e16ed08b0cc758 -8.5298842062601e-295 +bcf9eace606c08d6 -5.754777001098281e-15 +4dbfabe62c3a2547 3.3353962349191366e+66 +68c9bc3a6bd9e0cd 6.011679995030188e+196 +86b7c67c55f7c04c -2.6824437818673098e-276 +0632ed10513dbccc 8.341104329593578e-279 +d6d4d42b600374b5 -1.9566917146630816e+110 +7a7faa4aa6b0ff1c 1.1495773966967208e+282 +a8eb932233e5d9e2 -1.4332536407842886e-111 +4ed2a164606a2ad1 5.143302261900873e+71 +b8ac3fc722f26ac4 -1.0626142551015946e-35 +db76c8c52357bf11 -4.043086201838707e+132 +6287a64f6342f42f 4.35805108207153e+166 +3a0431ca47b29a07 3.1861291739645773e-29 +bad9da79513d9ef3 -3.3414753975611676e-25 +3be963f22d3a390e 4.3013034528043134e-20 +078b89865d37cefc 2.5451525259199287e-272 +1f1fffe7d9639e61 9.104314991299737e-159 +64a6b937ffc0e11d 7.19387523981922e+176 +3e63be0b731a879f 3.6773015199957006e-8 +62a07295990eb550 1.2123555897939754e+167 +24456a40df4ffc3a 5.892655869075368e-134 +5e8b8e58280d2d42 2.7527361603511375e+147 +3b807733718e2798 4.358415631334939e-22 +9f6db51f0bb9ddd7 -2.7046959759574186e-157 +b81c0806973b3bac -2.0594183898265177e-38 +ffc684087e1826db -3.162220112946442e+307 +26a0dcb16c9ac056 1.2753899121321482e-122 +04cc7d3230552056 1.4967595793145854e-285 +54e125a05a5b5f96 7.500948030015448e+100 +79d84b237e98cca9 8.612837949217414e+278 +c92e533e6923f770 -3.3813695647534623e+44 +19034ae2fade6c9e 3.464031331917281e-188 +279b21d42aad37c7 6.724590090153894e-118 +8cb7f952779d7c68 -2.1430017556375842e-247 +6ae7bb8e22805c90 9.524299532990639e+206 +3aee79a165127829 7.877633609976948e-25 +3763b92fc1717699 7.075417352241543e-42 +dbc11579c8151234 -9.700962845639305e+133 +18dbc92df6a73510 6.2362933710559606e-189 +77104f5c9cd13f7a 3.2869291118748916e+265 +28f847ef31400384 2.524108773249206e-111 +754cd4217a54e283 1.0821573432849514e+257 +3b2d090c26c48b86 1.2008735852569757e-23 +b2a6a97958f92311 -1.0759431547655781e-64 +4583f1a6da4c3517 7.715442811062959e+26 +b2b54014932d08bd -2.017837957133166e-64 +61291d686b86ddec 1.103414606107564e+160 +72701a8ed7f52b57 1.718079758086265e+243 +7622a7057bf8f4c1 1.147153718095194e+261 +3a52af3ebb7a1568 9.433287546483873e-28 +4a28a6e82ca0bb42 1.8014454712209577e+49 +ce3dcf4416d76ab4 -8.0366610800506455e+68 +b9316be814b4e961 -3.355260783027187e-33 +763c18bb856df8f3 3.4559718607554958e+261 +894d12c8e9837fc2 -7.213222170090852e-264 +648cc908e40dc9bd 2.2782328977463126e+176 +e0121122755c1d6e -6.055948868238215e+154 +c51ff90e98b43ed4 -9.663209730048487e+24 +96d208ece80263a9 -9.424444556859565e-199 +c3eef0b82763ad2e -17835874358911398000 +12bbf37b398be1f4 1.9795298054315576e-218 +16c89f1ec7500b37 6.433221439524593e-199 +27d74e0e227886e1 9.24163887331123e-117 +8121c6d66f99efac -3.240303957749324e-303 +42eecca2762eb773 270913982526907.6 +748aa861c23b56dd 2.4430352084710338e+253 +1bc856648bd13730 7.687550605292692e-175 +8c07ee4a22d1595e -1.0445065737245995e-250 +1fa56496f1316c6c 3.116323197525833e-156 +19daafce966d95b4 3.925349083547379e-184 +3b497edc65aca39f 4.2178850149773134e-23 +bba193f4d80cfd18 -1.8611383546705447e-21 +e505eca5e8e4a2e2 -4.442171213343151e+178 +be146f99280f9327 -1.1895277265307172e-9 +3b9b53b2a05da5c1 1.4466763894822972e-21 +ea22102efb584eca -1.769791834755294e+203 +a8c1721360ca0cd7 -2.2669280713150847e-112 +b7e27212af0e4d0e -1.6939606068237128e-39 +372a6779244ad214 5.9200246430339145e-43 +e9ddf49f1b9442b0 -9.171797351927725e+201 +40641a09c48fc635 160.8136923606959 +131970bee5c3f4f9 1.1531008324799055e-216 +5fc99d8a4cca6d50 2.6831738267051495e+153 +27d66b7e7c76ad8a 8.890687358135567e-117 +441e46918c366251 139621830457692540000 +522732001b56bad5 5.767784346216963e+87 +4b1fcc201c5402e0 7.613956254283195e+53 +43956a55ecfa12e5 385785074656000300 +3fc81f92c4fbff80 0.1884635412061435 +5b4573787083e87f 4.758135543620429e+131 +a7e592bcfd49106f -1.7109849164311507e-116 +c78680fb07e4798f -3.739090176909725e+36 +b039dc4fe2bc32bc -2.2333644836308812e-76 +0a17e81c9f9065d9 4.8589580113422645e-260 +50c8cf0368a91352 1.4707942012075033e+81 +af1e3fffcdbed5f4 -9.965670506393014e-82 +63b90b982d90555f 2.419711651956218e+172 +76237cddf71fc235 1.198528107201239e+261 +7eff5535d2a5d0f1 5.3717473863009666e+303 +2c0e086aa44a8710 1.757552639546235e-96 +586d3cd99f7dd61e 9.216190925663769e+117 +385970c29d916a32 2.990512829338024e-37 +5401025b597a8466 4.541430540712301e+96 +8fef3e6db039a8ee -6.288948693817907e-232 +f2066d261050203f -1.8692413233561752e+241 +eb39728c93016fbf -3.267995233408295e+208 +a6b8fe3ba2cb62a0 -3.7807838748660793e-122 +52e16372b3c85c65 1.7710468430427537e+91 +28b17509440e759d 1.134215393103118e-112 +fc7b4a62c7c4ae36 -4.2552827510963675e+291 +6d63b1f3b3726aea 8.690516787965227e+218 +e34ef7b42cad74bf -2.3374106470075585e+170 +384880b80212e196 1.4401456039293342e-37 +57f86ac3693661d5 6.0130033015360435e+115 +d5c19b67deead856 -1.261929789833944e+105 +9397dbac4027d866 -2.768336618497468e-214 +462c43b576f14e56 1.1196717116606684e+30 +7ea54bba7b11f94a 1.1409324117057248e+302 +97de49b064c0c838 -1.0372685620978203e-193 +7511cfdaa26c47a8 8.357710192574048e+255 +d1334738ee827527 -1.4629368609680972e+83 +5c1d1090c17cd80d 5.2813389662610794e+135 +d2af5f3d213382fd -1.9970608331792666e+90 +ece0a57092befab8 -2.869216277027128e+216 +ad2c2cad51e8294d -4.3222296973180687e-91 +c3d21d4d60d60a5f -5221138181242060000 +0d115b88adc8d18b 9.930064730162973e-246 +3b772781514642e8 3.0644483361938904e-22 +d95d97e022f3746d -3.0566880737954673e+122 +ca44b155636c29c5 -6.048485667463615e+49 +ded3ee32d63420d6 -6.371112409449373e+148 +9d68ea88c0a4bd17 -5.281695231279618e-167 +83cb5e29ccf251ad -2.1939891840343495e-290 +1f2fa8ec15567e4e 1.801528673525722e-158 +77c2e047554e8132 7.790729923408147e+268 +166b2e3f790210b0 1.1096674198066023e-200 +4271925974403891 1207516480515.5354 +e38b6ac1a8f4bca0 -3.311065405096704e+171 +7d0ffb5c7aac7dd9 2.5532290008113735e+294 +faa3711b00397362 -5.646545404358313e+282 +b7d53d2d24019904 -9.752456758296828e-40 +e16ce280fb635b36 -2.030470905214526e+161 +75e8165c69259000 9.258811765083671e+259 +cf5189699cd3db20 -1.239391466440515e+74 +788c43776954b496 4.7780716953299045e+272 +2701e372b4c7f87f 8.659342565542776e-121 +ce5a543e8fc1884f -2.8393223286947086e+69 +f84e59a6c28bf89f -3.20676902081695e+271 +daf4d13203bf28ae -1.442978368704481e+130 +289deea81799bd7f 4.8618441989490365e-113 +6f65516ff5fc31c5 4.040131673385015e+228 +b9dd98c9e2ef18e6 -5.836945633440279e-30 +516b7e3d575e3f57 1.6690636175404265e+84 +242ff6545047b6e9 2.19871499377839e-134 +b618890b585fa3c4 -4.196938398400365e-48 +85fecb3a76796d83 -8.482193659352111e-280 +31ddd4038e4e9f98 1.7287322807743188e-68 +b7488c8c0048e4a7 -2.2016314727507875e-42 +daac93576fb9bcae -6.189888248839529e+128 +acedc36550322457 -2.8537235954362578e-92 +c3cbce92b92fb5df -4007400416587005400 +ee65e892dbe37d74 -6.335476130449967e+223 +eac38ec3a439668e -1.9622042960281316e+206 +014dce42ee7f77c3 2.1731705970853697e-302 +88a0ada6295e19ec -4.040974131930569e-267 +1c97a3324c095edf 6.116510987615614e-171 +fbec0b5c36591ba2 -8.540664012660464e+288 +7f6af482392d0115 5.915163559895815e+305 +ac38eabbaa93be57 -1.1665298357019675e-95 +31a15d11d735eceb 1.2579101436437572e-69 +d84669c33f99ddf2 -1.766245008080316e+117 +f10c3a73c1bdd2c9 -3.590143278351947e+236 +fb2f20e6854b3b2c -2.3144315904204965e+285 +30536c7ab15ee65a 6.709870172417155e-76 +592c1cb8d5628d16 3.629635648947069e+121 +3b3f2b8b36d1cd8c 2.5783296612123963e-23 +7c658b36b7508d17 1.6796090526326285e+291 +7a5764180c2589cd 2.1229736641426558e+281 +0d56c59ba75a119e 2.0844088977237104e-244 +206a735400d9023d 1.57821476423377e-152 +8b8f8f16802d9f79 -5.380665856349896e-253 +71c3aee761ab3a07 1.025374862408305e+240 +fcb8399bb9800db3 -6.043662025197897e+292 +4a1186baed960168 6.403675308294665e+48 +d74f2d7ed334776a -3.748976369028628e+112 +5f69ec33676a8504 4.24274948441573e+151 +dd954894a9b9910e -6.48845632465135e+142 +d05f6b461ff761ac -1.4552304564293103e+79 +b29ec3a34afb646f -7.303091100399519e-65 +956cc3dd0cf46967 -1.7919204806567018e-205 +9403413176313875 -2.8597557494017754e-212 +b78c5a137117e745 -4.0682922699462897e-41 +6c0475909d72b9cb 2.1523674359501565e+212 +f696654c2b2f4e78 -1.763034223438079e+263 +0598bd228cad8ee4 1.0647382223713905e-281 +38c433798af9ecf9 3.0395196928092176e-35 +1793a032f8c80160 4.200777655605781e-195 +caf98afe3dd1c962 -1.5290798667433095e+53 +f9ec183668147270 -1.9920881157706816e+279 +7b221fb302bec694 1.3475217286760955e+285 +9693f0aa2fea9e19 -6.512536212260826e-200 +992c1d9e081bed65 -2.0193030289087234e-187 +dce27d9c08a223ff -2.7524565561508536e+139 +2c95e7f9acb47878 6.563681330000184e-94 +9eddfc33e0ec0e88 -5.3319833140191924e-160 +4f454cd0d4d57495 7.526790233125043e+73 +36df358058cb5b3c 2.186655464713789e-44 +1007e37304df828a 1.9233651704019675e-231 +4c6f1a209b79df0f 1.5618463608285532e+60 +10cf20ef213b0f71 1.0265818015686991e-227 +37b8c1e8da0a852f 2.842016946348802e-40 +8de36268ae703be5 -9.084605510439713e-242 +b6fee3f4e1fa82d2 -8.657349532746307e-44 +943d32b3206d8357 -3.469259908700039e-211 +32285a5d36056d91 4.516512048032042e-67 +4fd87a64e694230a 4.428704039569761e+76 +dee3d31a1da889cc -1.2674553886551852e+149 +86d5349e05362bdf -9.570028671047148e-276 +d3a71a4a115d5048 -9.63809610759187e+94 +392d749489f2f8e8 2.8364510279079804e-33 +a2539ce5cd702719 -2.5130635849290054e-143 +a66e07011c2e2117 -1.4194789334276393e-123 +17d89bb46096d895 8.427582663401799e-194 +93b4a66f163b7064 -9.584422078873272e-214 +6b35e7ae17cf9f84 2.8130683097604826e+208 +9c5532f3888161bb -3.42845495674387e-172 +90b7929ab88f6db8 -3.886978456865125e-228 +7a218b2479541d63 1.9903197290543295e+280 +57de401dfc3f8d99 1.8623886178700536e+115 +1628374a5013b4c2 6.178953482283761e-202 +ef85923f7090e32b -1.6352443003311994e+229 +acd3948de676682d -9.386862773102878e-93 +87d9f659f25f2fbd -7.67869189456395e-271 +63648a8d8ec3dea2 6.201744149606003e+170 +5197ecd83dc3b19f 1.1619673099352777e+85 +4839e479e91fb13c 8.810756152294667e+39 +1d8ee2e429ba415e 2.6188957293625965e-166 +46684db983eaa25c 1.5404244162667704e+31 +c93e7f239e3e6018 -6.800977364939892e+44 +772c8dcfaf291ad8 1.1508862125037754e+266 +42ede00f24b0feed 262785311541239.4 +4631cea701bb8450 1.4108345735590082e+30 +b680476a9f63e758 -3.5643275964892296e-46 +224e12300363f696 1.9265513951096132e-143 +e53988ec6cbc54fb -4.138956600592406e+179 +4f6e6eaff4cb7292 4.30154782496463e+74 +4d93084ef64200f7 5.010878594425289e+65 +ea94954b095c5936 -2.5813652083298105e+205 +67dd09749b766c04 2.0699889703112217e+192 +3d22b99861e301dc 3.326224881384528e-14 +475b0579286530df 5.612121053514221e+35 +b7d102f13f9e2d7e -7.811294803312277e-40 +e031c4c3e28d8ea4 -2.3823816325721027e+155 +0ede041c0e8dbcb9 4.609533872141033e-237 +b24d7a177168a980 -2.1867194750617227e-66 +0b11aa32213618d3 2.3529520907932907e-255 +bdbad6188bac2022 -2.4407483611920924e-11 +8f709f8db47330e2 -2.614083615682069e-234 +cb1b80c689cdae03 -6.585667187264962e+53 +14ccd3b7694bd59f 1.7536895881983401e-208 +1a61155ca437c5f4 1.2865541593922235e-181 +1108c4b946ce577f 1.3069294366326307e-226 +aefc49cba51bf411 -2.3298474578892894e-82 +49aea34fc55ce7b9 8.745584681089136e+46 +365bf762b1749036 7.654141185791315e-47 +d43bb867d218b496 -5.921027443560549e+97 +fb3036a64dfd607f -2.410971163008958e+285 +91d0928518cf657f -7.163530942125446e-223 +e178aaef45624475 -3.4680598900888077e+161 +e4421f29f08d3dbd -8.964111342003967e+174 +781c2053f698e886 3.71474013185876e+270 +1995a6839bf138d7 1.9903503102007503e-185 +10b3d172595e0f66 3.267882481562712e-228 +2892f66a7e73aaf8 3.080056360458856e-113 +7b1d80a2a460f3ad 1.0967672290505315e+285 +459209441575790a 1.395483006482003e+27 +f94920bc4dd09178 -1.7399700571319539e+276 +f44cb2d48ff46e5a -1.6437903725387137e+252 +7c0aa31af6375006 3.2448399714324232e+289 +747666385a43ff26 1.026386120464926e+253 +a6958472275c7158 -8.137498133265166e-123 +f806c353a5519fba -1.5031957268444934e+270 +fd00a58141819205 -1.3289505109787805e+294 +3137cee771e4b061 1.3474974457865919e-71 +244e56d4d2a54141 8.348257709613734e-134 +257b11e98d7e7b83 3.905257094121928e-128 +1fc53112c1e207b4 1.2348036177441014e-155 +f76d1fd18160aebb -1.8781986596604601e+267 +577ade9f2dd2a126 2.5847577906462683e+113 +de160f5828cd8de4 -1.721639561358429e+145 +ff68c4d2c5bdefff -5.43539741962478e+305 +14861c38a5728be0 8.406717912788309e-210 +c3a0b94af40896f7 -602538393637452700 +3d281633c52fda1a 4.278662304723932e-14 +5b07966b944c432e 3.2700282766627467e+130 +4acec1087841d028 2.301290148890984e+52 +b0f2941cf1d05988 -6.571935055133667e-73 +33d0861b81629b3b 4.113128632008241e-59 +80e2aa985c3bf0cd -2.1265454330156622e-304 +50a0fa9134be146c 2.5164904918817525e+80 +3c10ce011d57f344 2.2774621419168866e-19 +bd7a2582afbd0da2 -1.486257893214335e-12 +31122cbf5a675f39 2.5716423310634405e-72 +ec9461baa96d7fc7 -1.0978385765879335e+215 +859e23cf4168b4f8 -1.2971960083007331e-281 +498f22c5247a5b91 2.221926409567801e+46 +fe01deea2d1a6b66 -9.349938454704566e+298 +80a8c7137ed64630 -1.764226616634148e-305 +e24a7760e80466bc -3.048184397821392e+165 +e0966e8026f84171 -1.924858625259898e+157 +e00bc79c0bedcdb3 -4.655815186195314e+154 +f511541244ef1ae8 -8.130830181062495e+255 +ab414b4c45477edd -2.470871930596125e-100 +a7ea55f0ad09e760 -2.0887026302367514e-116 +3d2c8d5d6b9a2cae 5.071890770653972e-14 +f102cac994bdf377 -2.3900270454016616e+236 +365662a3fc24b551 6.126661548574415e-47 +c9cc51aa34ad887b -3.233458579162101e+47 +895d1042a1ffadab -1.4421551014076024e-263 +f6e8b3311e825830 -6.222180435777359e+264 +facea39e362f5080 -3.5594459872122978e+283 +11c2c89f37c52e21 4.059699520837477e-223 +50363671455f0c3d 2.5720509895457543e+78 +4a1bcdc52b5b1d65 1.015882108489761e+49 +6f3a06b3b058521f 6.165483747840077e+227 +7f003e86c199857b 5.569871027259131e+303 +f6d072d18bb688b1 -2.0717758734558953e+264 +573b7f0bc7d5e9e0 1.6531499315375862e+112 +5f5683c6ec7a3a33 1.8424896140550757e+151 +8bda72217d642287 -1.4428484888301016e-251 +0678a4bbb4c2ab02 1.7377456268455364e-277 +09c54144fbc9aed4 1.3500008603959969e-261 +abad6e9fa49d0889 -2.6912436810009235e-98 +c8025c367c3730d8 -7.809568104513652e+38 +bafc6ca72a6a68b9 -1.4695083687498658e-24 +bb0f9f9ed7e3b228 -3.269795072782646e-24 +bb1c14239a7dff4c -5.806532503999951e-24 +3b2f4c1c52bb57b2 1.294426286108704e-23 +bb3cc3bcb5078d9d -2.3793517952749486e-23 +bb451bccf7b145c6 -3.49212432460402e-23 +3b57c95f872d96cd 7.870330339965773e-23 +bb6722f6fcdfdea0 -1.5310505150417812e-22 +bb72549c4f3fc91d -2.4260228109685234e-22 +bb88bc21e2ee51cf -6.547271414634629e-22 +bb981870edde1eac -1.2756037280142429e-21 +bbaad96fab758991 -2.84278658726958e-21 +3bbb52bf55f726c2 5.785919443517358e-21 +3bcec5ebc253380f 1.3032926927520782e-20 +bbd3f6ea1d667dbf -1.6910597584037197e-20 +bbe716c4227c3e84 -3.911416932488739e-20 +bbf488617d15e7f9 -6.956762083655289e-20 +bc025a5e62851276 -1.2436478373280502e-19 +3c137fb656ee45d2 2.642590469241124e-19 +3c210cd012daadd9 4.621425299715539e-19 +3c3b3b48cc6b4ec2 1.4762268863454993e-18 +bc439b70ff4668df -2.1258161192681447e-18 +bc521be365a533ce -3.926750104254872e-18 +bc6ad30ff8094a29 -1.1633256170851833e-17 +3c78438b2914f2fd 2.1045528315149127e-17 +bc843caf6c95c458 -3.510568877307717e-17 +bc978df25c16f9d0 -8.172101664097987e-17 +3ca1d1a9226ca023 1.2364406052623482e-16 +3cb8c2075c4211a3 3.435852270824105e-16 +3cc399493aca9d40 5.439752439008674e-16 +bcd0f80cf944587a -9.419658366060097e-16 +3ce530b5263b8e4a 2.3525919123538445e-15 +3cff35e0e4f12bd6 6.930114896333231e-15 +bd00573dc40afc01 -7.256766841301805e-15 +3d11851871cfc9b9 1.5560800865303442e-14 +bd2ef9e5fb3d186a -5.5024723422445065e-14 +bd39fb0f2d3438da -9.230198943372106e-14 +bd425c62b52d262b -1.304619073059209e-13 +3d576945b28596a7 3.326934425327803e-13 +3d66340882c5d44a 6.310544581815814e-13 +3d75fa6082f80d62 1.2493066577751501e-12 +bd88e14e37a2e5f8 -2.8285398629725543e-12 +3d915a62a3adde33 3.945630764375668e-12 +bda398bde4c5694f -8.911523558233619e-12 +bdb0aa93a3c5505d -1.515792546660124e-11 +3dc8e4ec04a76dbd 4.528233394306854e-11 +3dd59f35c08eec26 7.86600646940818e-11 +3debcb0a25cc387e 2.0222158921250457e-10 +3dfa11a03a9a7f8e 3.7935171202540116e-10 +3e042ddc426368f1 5.872903316236911e-10 +be1fa49c6be3b260 -1.8418657012403435e-9 +3e2c8a9a079ced04 3.3226577573555153e-9 +3e3767ed62b415de 5.449626121249144e-9 +3e4d51b30325ca75 1.3652787432472682e-8 +3e53ed8b6c5dbca1 1.855931121931051e-8 +3e65b1fcc54e0f98 4.0410576807584136e-8 +3e7b806f45ba7587 1.0245180831000141e-7 +be8d1230ba4480ac -2.16596245946649e-7 +be93c7799f5fa2ad -2.94733040971785e-7 +3eab72c3628c16a8 8.180229019444964e-7 +beb6d39c8c93a485 -0.0000013605718314719053 +3ecc436ce0eb028c 0.000003369257462665641 +bed05bb88ca9f789 -0.000003900119006970234 +3eed16c125706626 0.00001387066110507529 +beffd7c46933cf6a -0.00003036769937821098 +bf035da9b0173274 -0.00003693746658548362 +bf12a69602d50110 -0.00007114687533249877 +bf29d164e596ef12 -0.00019697529459771678 +3f3acf45189c464c 0.0004090827647909897 +bf46b264e6f56dc0 -0.0006926529585302085 +3f52e550d1edf71b 0.0011533059366394874 +3f6a4532a1ba8a35 0.003206824197904435 +bf7d3f1c5fdfd261 -0.007140265310216693 +bf87bc3fc1d2d585 -0.011589525320554692 +3f970424f288b16e 0.02247674684819983 +3faec39978743b8c 0.060086055711614156 +3fb856b9b10db0fb 0.09507332394213457 +bfceb060dda8f55a -0.23975764106027492 +bfd01f890ce5b230 -0.25192476520058715 +bfe89fc47b7ef554 -0.7695028697996045 +bffa55b549e3ddbe -1.6459248434383507 +400685114b3ab585 2.814974391678961 +4015864cf2ed01ae 5.381152911868794 +402400db9027702d 10.001675133527977 +40324d87ebc4958d 18.30285523938578 +c0477a91384bdfe6 -46.957556759880205 +c0531143908271d9 -76.26974880923025 +c069479d3ff0237a -202.23794552709643 +c070ee7346905dbc -270.90314346688297 +4085e4d72bb39207 700.6050638226451 +c09c8923328694e3 -1826.2843724277852 +c0abf11885cec10a -3576.547895871224 +40b2eecaa3445e08 4846.791553757626 +c0c2b47fc5bc54b4 -9576.99822191368 +40d34938dd8e811d 19748.888522745216 +c0e8c540e9087b5f -50730.028446427306 +c0ffd28f9d7d5c69 -130344.97594963167 +4107d0594fbf9c91 195083.16393968885 +411b22f62e85e7e8 444605.5454326854 +41231250f3121627 624936.4747473643 +c13cd74063235bcb -1890112.387258279 +414db2075f328fbc 3892238.7437305134 +c1529ae3c79e85a3 -4877199.119050416 +416511dd57d4b068 11046634.744713023 +41767ae5884a0308 23572056.518069297 +4182dd0dbba7550e 39559607.4567052 +c19aabe493ec9824 -111868196.98104912 +41a81039a2c1c814 201858257.3784796 +c1b6cbc0c5fcce84 -382451909.9875262 +c1cd8bf18ea61619 -991421213.2975494 +41d990aafe17fc75 1715645432.3747838 +41e935c20c006ecd 3383627872.0135255 +41fbbd1e7f7a7d50 7446063095.655594 +c20ae77769e5ddaa -14444064060.733234 +421149b73cd63c91 18562797365.559147 +c226201489e294a8 -47513748721.290344 +423e0dc87dc37b81 129080262083.48244 +4243a95888fa3d36 168891060724.4782 +425388b3c95a44cc 335591449961.07495 +c2611bfb364a4345 -587871138386.1022 +c273947557d8a683 -1345521548682.407 +42867beef192d70e 3090193199706.882 +c29796dcf065f120 -6484179818876.281 +42a1ed7b0b5972c2 9855834631353.379 +c2b97b9e640530a7 -28018729026864.652 +42c69d5b1999f306 49730188162022.05 +42d48af97576c160 90348493069061.5 +42e2571febc474e2 161323255997351.06 +42fa5e16a284848b 463862543960136.7 +430347b120f5cf42 678356330658280.2 +c316c537ae97b45c -1602323107671319 +432f95dc8ac40c34 4445249365739034 +c33e8a25110c092c -8596141105744172 +43400e472d75da3e 9038596991071356 +4355b002b90a7e0a 24418001007278120 +c36a4ea0e1caa876 -59238418274927540 +4371616746e1e0a6 78274732845042270 +4389aaa999cecae7 231184634381688030 +c39f00857d770eb2 -558483047272656000 +c3a3e745d3c60c2f -717095891101947800 +c3bbfb49bf896bf7 -2016286345171564300 +c3cc6e852a1264e0 -4097442592487751700 +c3dc22d618117829 -8109672724523165000 +43e912e37e487099 14454052256397707000 +c3f09ea7e8cbe638 -19161266707555910000 +440a993016ccfff4 61331711709107090000 +4414369e6c361b0b 93217645894549290000 +c42988fd35f3d5fd -235519853561194120000 +4438f1ba13feed16 460140114263458300000 +444c87236f58954c 1.0524931666188504e+21 +c456410803c6f766 -1.6420574767479128e+21 +4469d6d2fa806308 3.8131864971303107e+21 +c470a888b090a1ce -4.9166728913567e+21 +448ffe27f7fa330d 1.888521425115814e+22 +c493972848aea42b -2.312833107290834e+22 +44a39d26c05593a3 4.6311947159978606e+22 +c4b51e2d4a4db044 -9.972636196877327e+22 +c4c31d7141f5ba24 -1.8053615965368085e+23 +c4df5d9dd9fc5850 -5.924811302704165e+23 +44e3b3c87f93b4e0 7.443310223380157e+23 +baff93792a0fab89 -1.6324444321874137e-24 +bb092ad443fc769e -2.602237975155112e-24 +3b19f0854953e9f3 5.364169862359892e-24 +bb2e1f16cfc552e0 -1.245793635091097e-23 +bb3e2dc779d86045 -2.4963338959196499e-23 +bb46550f4eb161c5 -3.694563299081007e-23 +3b5c333974c0d246 9.330628894665998e-23 +bb67733028910262 -1.5517877579816153e-22 +3b75ad57bd00f8e6 2.868942947988351e-22 +3b8bdef24c760097 7.377361748998659e-22 +bb9496aa27d02956 -1.0899479075730842e-21 +bba9548abbb71c84 -2.6819436815713514e-21 +3bbd12d407edd3bd 6.156563227642605e-21 +bbc640cf6055475a -9.424581676140786e-21 +3bda35c2b5877b54 2.2200735474588642e-20 +3be5db04ff1c20a2 3.7024733374964366e-20 +3bfea59255387063 1.0383527572088141e-19 +bc0f55efc1047843 -2.12338892165142e-19 +3c12a9b4d0760073 2.5292966583558674e-19 +3c2fd36c453e90b3 8.626419572835303e-19 +3c317f7142a2a152 9.485588294074041e-19 +bc4270c0bd85f090 -1.9993166176403557e-18 +bc57f49f5d72df71 -5.194533327229376e-18 +3c6bfe2ce98fc68f 1.2139973405050714e-17 +3c7a69221ef89ac2 2.290761061296746e-17 +bc8839dfc1559b77 -4.2025533198551294e-17 +bc9d97c883dd0d81 -1.0267100838779193e-16 +3ca1a6efab9df7da 1.2248601147069066e-16 +3cb5f1a3d8267ab5 3.0453287553050025e-16 +3ccafb0e2f5e3b00 7.488644481302964e-16 +bcdfaf44ad024776 -1.7588509348848397e-15 +bce82cdf90d75e78 -2.6839959525098203e-15 +3cf6baa42c666ec4 5.046866832860727e-15 +3d039bad964f9319 8.707753398221356e-15 +bd1e9cff781aea7f -2.719004856824652e-14 +bd204b249dc9a06b -2.89431189615647e-14 +bd3c3402014149eb -1.0019773665840144e-13 +bd42b49bdf5cd1b8 -1.329105957790349e-13 +bd58472ca9d7061b -3.450114897414076e-13 +bd6c42bef8284ed7 -8.032181557740247e-13 +3d7a06a211054b8a 1.4794017282804328e-12 +3d83c2027a3ee39d 2.246207521237386e-12 +bd97105c9f35d91a -5.244126736731629e-12 +3da13a00380eb428 7.833735181194965e-12 +3db4e5d6abd6a340 1.9006444629874626e-11 +bdc3c24e9b4dde06 -3.5941433347755635e-11 +bdd6789d66b8043a -8.17495738461776e-11 +bdef9a0487973ab2 -2.2993213218428795e-10 +3dff8ce27c8ef847 4.5911774083785473e-10 +3e0f6d776c2f1477 9.146636437555346e-10 +3e1b948a0482b79f 1.6053807329004741e-9 +3e2acc190cb5b530 3.1196113241940545e-9 +be30949b81d3925a -3.860447986648473e-9 +3e4e83977783e3fe 1.4209202467840788e-8 +3e545e6a7299dbb9 1.8969934212409942e-8 +3e6b14e377e1264e 5.044340321885729e-8 +be7eaff6028ab782 -1.1431927816426826e-7 +3e87badc1e08026d 1.768016906350698e-7 +3e96899d762b2929 3.3583579843163563e-7 +3ea8ab46895e153b 7.351948336653739e-7 +3ebfb8449ecc87d1 0.0000018906472362763345 +beca4d9fa116ca9e -0.0000031355878113645673 +3eda6fa503a3808b 0.000006302860180634868 +beeef0e5dbc1854a -0.000014753822022042302 +3eff8e76f93e14c0 0.00003009462632476835 +bf00a051280358e9 -0.00003171203297973482 +bf1ff36b69a84418 -0.00012188284964788387 +bf2b40b0f8c91290 -0.0002079216032077236 +bf30974ac04424b5 -0.00025315833069379105 +3f427c6a832321bc 0.0005641479567879896 +bf5f87e3eb2488ff -0.001924488617597364 +bf6334ec48931e2e -0.002344571582032071 +3f784e8530247392 0.005934257760853339 +bf87830582c09ff5 -0.01148037247919829 +bf9b2903ad08438a -0.02652364486317852 +3fa9aa7c0329f18d 0.05012881792660826 +bfb85889cd3e612e -0.0951009870608501 +bfca89df71c1966c -0.20733254484655406 +3fd4de3c910a0e40 0.3260642448806017 +bfedfc42ffdb8a5c -0.9370436665743216 +bffa93e4a91d2a48 -1.66110673961761 +4007ce538674821c 2.9757452492051595 +40129dd6f42b6c18 4.654140296125682 +c02f0ee3471b3cc0 -15.529077741694323 +c03b214855b03ab9 -27.130009990238907 +c04bd32c075ad48c -55.64978115019866 +405c6981573ac225 113.64851933229563 +406b7d21fea29ee1 219.9103997398552 +407b3038c11c439c 435.01385603943504 +c0830fb147cb1c1b -609.9615627162888 +c093c5076dc6ece3 -1265.2572547037205 +40a2f09abcce39fa 2424.3022217221996 +c0b29e404b149b09 -4766.2511456373695 +40ccd9863d3b507f 14771.048743643098 +40d56d28a7a4d91f 21940.63523217395 +c0ec10d3ea24376d -57478.6223317225 +40fe856c4a960443 125014.76820947327 +410ba432461c588b 226438.28423375296 +411b6a066b6322fa 449153.6048703637 +c1236660ef5c1a3f -635696.4674995615 +c13e70fbe155cdcb -1995003.8802155133 +41427a72137d7490 2421988.1522660926 +4151ad44d266aa79 4633875.28751623 +41698b29a5fdd96d 13392205.187237466 +c174233281d3d99f -21115688.11422121 +4186e1c518cdf9ec 47986851.100574344 +c19203262c81991f -75549067.12656067 +c1a5cc0108473ded -182845572.13914433 +41bade992d8968d6 450795821.5367559 +41c009fb4f9c8f87 538179231.2231301 +41db8169a160b758 1845864069.5111904 +c1e6a1e67c88a036 -3037672420.269557 +41fa96157fb37cf6 7136696315.218008 +4201858b1913e78f 9406866210.488066 +4216e937b029c380 24600505354.440918 +4220a59e0d477007 35749037731.7188 +c231dffcb0cf1f27 -76772323535.12169 +4240e66dc83fa8d9 145170862207.31912 +c25997696acec1c8 -439657802555.02783 +c26294608f042e4b -638390138913.4467 +427ded3aa376d4d7 2056545515373.3025 +c2892e190863ecbe -3460722396285.593 +c29a8534d6d1c4e7 -7289854866545.226 +c2a6c0f24ecdbeae -12508977391327.34 +42bf65ed324c4e53 34522631654478.324 +42c923c1b6ef220f 55282729082436.12 +42d2e321551d9f8b 83066904409726.17 +42efc253afc92674 279355905165619.62 +c2f520e68c61a938 -371696817543827.5 +430bdae024e0246a 980060074673293.2 +4318819ba569bea2 1724476234035112.5 +432afdedf526855f 3798773928247983.5 +4334624348ec7f95 5737540660002709 +c34ec669bec4e22a -17324813551518804 +c35a77db0218a16e -29800527644755384 +4367998531410bac 53141774403132770 +c37825fb8f62e2bb -108754589019155380 +438f83982fefe9be 283853246097602500 +c3908d922aa799ed -298192547055303500 +c3af85054f150d9e -1135613712146026200 +c3b6304921718005 -1598858275703489800 +43c314e85bd9c245 2749958534895799000 +43d9a900c4544081 7396039860957611000 +43e09350885ce840 9555094982302499000 +c3f0b1a3205e4190 -19246750958404305000 +44070f7a8b9a9913 53173808881144590000 +c41798a6dacb88e1 -108818708350732090000 +44280c785be007b0 221810213456224700000 +c43e4e4a04de2bbf -559043649046784700000 +44444499527a9828 747755908453398200000 +44581af1634d6718 1.7786531994699836e+21 +4460e697899d3bd6 2.4941104467037305e+21 +c47a1eff0ef83421 -7.709581861053386e+21 +c48f0407907eec09 -1.8308461627545123e+22 +c491f32c3a463391 -2.119149398889225e+22 +c4a07714c2a07bb6 -3.88772611025826e+22 +44bed20461cf16c1 1.4554512650271389e+23 +44c012a3c94918ac 1.5180341432781982e+23 +44de5c4dbcb2219d 5.7349478595505846e+23 +c4e51aed4a4c829e -7.973312803939051e+23 +3af085956b9d7c1b 8.541576205437313e-25 +3b081f9b49d02023 2.4943076397350444e-24 +bb127ed3de09e8a3 -3.82476328136622e-24 +3b2678afa1bfb3b2 9.293965586834384e-24 +3b36a54da7cc6055 1.873209738008355e-23 +3b43e8b5d83b4dd5 3.293671854171798e-23 +3b5a30d008feee0e 8.665767225541261e-23 +bb644486ea315052 -1.3412027972511174e-22 +bb78f3858d5f6672 -3.3022713143297483e-22 +bb88c80e352e043e -6.559599642123745e-22 +bb9ad8338d2e97b6 -1.4211379353298647e-21 +3baf360abd55e719 3.304603897896039e-21 +bbbdbe08149baa97 -6.298179293483329e-21 +bbc4ebf7e70ed802 -8.860706393254733e-21 +3bdb23d51a128532 2.2988449138416716e-20 +bbe88bd53d51acb8 -4.158291842195862e-20 +bbf5e589b51521e1 -7.418867663707989e-20 +3c0c949be0cf53e2 1.936690249903675e-19 +bc158a1fe52c75c5 -2.919153252222785e-19 +bc28c4d0e79ffc6e -6.713600116930204e-19 +3c3dc1d76e0e1816 1.6131406892477621e-18 +3c4742e3031ac1a6 2.5219926440366243e-18 +bc5683f5e0d25da5 -4.8822644177797375e-18 +3c6e67f0148c305e 1.3186503574879889e-17 +3c7766eeaedeffbe 2.0298068360609088e-17 +3c81d1c5c0496dc1 3.091177260798877e-17 +bc95934a2a9eaf5a -7.485453382393019e-17 +bcac56a3c5830484 -1.9663740396550728e-16 +bcb7fafcc543a38d -3.32795172984612e-16 +bccfe230d744678a -8.849464981031196e-16 +bcdfbd2d70884dc5 -1.7618670191101707e-15 +bce9e49f19fe8c1e -2.874706328186813e-15 +bcf6a074e4e1b686 -5.024155238940322e-15 +bd0debbb2b43146b -1.3287515408931865e-14 +bd1825753e5be883 -2.14462405569614e-14 +bd28702100e91f92 -4.341061482605347e-14 +3d3cde268907692d 1.0255894090088511e-13 +3d470ecd71948cbf 1.6383568153109433e-13 +bd51a238cb0f6a66 -2.5058965172092397e-13 +bd669e31ce6b2d32 -6.428407313073555e-13 +bd744386bd4af7e8 -1.1518622335681343e-12 +bd8305e68c7e9807 -2.162670301187242e-12 +3d90d040842e84f7 3.822943754390565e-12 +3da58d76d665043e 9.800985283629011e-12 +bdb2250b016301b6 -1.6502507768869802e-11 +bdc5fc83fbfd6a35 -3.999300846978399e-11 +bddd34d6d554bda1 -1.0625227549600441e-10 +3de6d56a03515c9a 1.6613666142364718e-10 +bdfcf30d19d598a3 -4.212694861650479e-10 +be0741bf33307b52 -6.768626547952838e-10 +3e13bad3906a8f24 1.1484249676793854e-9 +be2d83555a79133a -3.435767854060784e-9 +be345f9693d66f9c -4.743549828457046e-9 +3e410dc4ce1fa443 7.941287131318924e-9 +3e5e6eeae374ebcb 2.8343192873632345e-8 +be6e7902574990dc -5.6759811882508185e-8 +be7708bdda62b4fd -8.580888408395419e-8 +be818d5fb24ceed4 -1.3077438967302478e-7 +3e9fc87287db4209 4.7360357045576184e-7 +beab433251df56cd -8.124854138323525e-7 +bebdc3fa5e30fb20 -0.000001774164382309109 +bec31e809c7a52d6 -0.000002279180282569559 +3ed2f80c6e6ad3f3 0.000004522547647202609 +3eec5fd383549a94 0.00001352993067855788 +3efba7aa4f6416a1 0.00002637380836125489 +3f0eb3ccedb356cc 0.000058560077116060616 +3f1ca4ee74a60361 0.00010926919381938411 +3f20cbe92fedb727 0.00012814733051008076 +3f3255be3b294589 0.0002797688895600161 +3f45d2800f3b5542 0.0006659627237818671 +bf59ce71cd883368 -0.0015750991207170387 +bf658ccab86ef90e -0.002630611360898822 +3f717173a779a77c 0.004258586668223315 +bf88aef5bcd50b32 -0.012052459550096015 +bf90324d98c02da4 -0.015816891143953918 +3fa09ecbbfb044ff 0.03246151652286677 +bfbd626c3b2dfbb6 -0.11478306240859024 +3fceaa648cd18dc1 0.2395749747839613 +3fd07fafbd277e9a 0.25779336425873967 +bfe14bb8e99fd6e7 -0.5404934466339143 +bff610ed6471b08a -1.3791326449964196 +c00e4293f612316b -3.7825087761213374 +c011e2974ccd2fc9 -4.471280288718796 +402484d43b4a67d6 10.259431698639599 +403a6505806dc699 26.394615198899007 +c04b313d1e679b54 -54.384677696789964 +405f567a0c130162 125.35119916778561 +c06c0e19e3273fe0 -224.44066007295532 +4070f1bcedec899f 271.10862533948415 +4085de94d0bed74c 699.8226637753919 +c09fb28dff87f9e2 -2028.6386700865046 +c0aa04126798ce91 -3330.035946631637 +c0b7411c4a2e7b4c -5953.110506920912 +40cacd53df3974ba 13722.655249769206 +c0da674349eb68d3 -27037.051386692398 +40e97b130d3ceac2 52184.59536596156 +40f37e093e6a6397 79840.57773817923 +410b488e9fdcb290 223505.82805766584 +411d6978bc77aff5 481886.18405032094 +4127303f9ece1dea 759839.8101662968 +413761f2e76bbb51 1532402.9039876054 +c145f1269fef9867 -2875981.249499369 +415994eba714be97 6706094.610641143 +c1642c8686f3d22a -10576948.2172633 +c17bef3fb353ff0e -29291515.20800691 +c1804f31d1cb5b70 -34203194.2242955 +c195ee42885c11c6 -91984034.08991155 +c1aa74d7a8ba1865 -221932500.36346737 +c1b25400c04bd515 -307495104.2962201 +41c11ba8e662a7ff 574050764.7707518 +41d7789a81187d70 1575119364.382656 +41e03bbedebf118a 2178807541.970891 +c1feb24774a54d29 -8240002890.331338 +c20a251ea15c6df3 -14036489259.553686 +c2170fa0d56bc9e8 -24761611610.947174 +c2273d3b7ff8647b -49905778684.19625 +c23b25422f77ea73 -116589211511.91582 +c24131567953f967 -147684389543.94846 +4254b56730d9e755 355771138919.61456 +c26beb8cb79d21db -959327878377.058 +c271d287f7310d58 -1224745120528.834 +c283845767b33942 -2682390378087.157 +42927f44ddfa4fb0 5084456386195.922 +42a1f77acd7a572f 9877307440427.592 +c2b88aa93c128841 -26983823839880.254 +c2c4e1f001e29d11 -45921253705018.13 +c2dce1c7b2323c6b -127024174254321.67 +42e9f306b3f222d3 228252641628438.6 +c2ff5bbc22eb1256 -551661742240037.4 +430d8a1fa1c491f2 1039330348405310.2 +431a39e1353f2451 1845497204558100.2 +4322ab5480a46e59 2627464502327084.5 +4336b1ddacff90c5 6388015135953093 +434aeaff2712fea4 15153461975186760 +4355206ea685f259 23786536492845412 +c36c6ca9fb054ffd -64006213316804584 +4373a1d0f41b1a22 88415094051021340 +c3892ccef95de496 -226756540042613440 +c3940c8bfafabadf -361170872635930560 +c3abd0c7342e186a -1002160430552724700 +c3b775b44f791eda -1690455488574970400 +c3c0eb55eeb51a67 -2438325215725670000 +43d4d4ea58440993 6004328962562870000 +43ee555416448540 17485965190190727000 +c3fa61f107755c4a -30417048513009720000 +44044ed65b41e472 46826963724091150000 +4419ceb56d743b3a 119015883382828470000 +442a2653665f5dcc 241188504736369080000 +443af3f79c131077 497195037192606900000 +444b03b682cfcb97 996659270094037000000 +c45982dc06ca51c0 -1.8823920841142819e+21 +4465cad62cadb563 3.215980354505298e+21 +c4798b36b0a7d6c9 -7.539200020762603e+21 +4487de4c18a20b24 1.4089386200135052e+22 +44983308e5110632 2.8569555118467737e+22 +c4a9dd4c4120e8be -6.107069361061765e+22 +c4be2cdc015146ef -1.42498504266861e+23 +c4ce4c10ba9bbfa2 -2.8614830496557783e+23 +44de961f0a4f205d 5.777609711372446e+23 +44ef6a885122095d 1.1868683080836648e+24 +3af9ec3e3c37ba0f 1.3401786225001236e-24 +3b05e22ed796b6bb 2.262703684888016e-24 +bb18ca2b136f5516 -5.1263938994705596e-24 +3b20bb6a68460e3c 6.920231219053822e-24 +bb31b42b76928d20 -1.4644230363979534e-23 +3b47f9bd150ce03a 3.96642060692706e-23 +bb5e7b99deedb77a -1.0085917973700812e-22 +3b6c7b6105faf294 1.8847772954717634e-22 +3b7eb557a3fd2271 4.0642187357939906e-22 +3b8ec6d29f566475 8.146511779263256e-22 +3b9a9aecc88575fd 1.4084662648870856e-21 +bbafd016de5aa056 -3.368316400483953e-21 +bbb59343b068c096 -4.568737238403309e-21 +3bcaf4d1dc04f497 1.1416448638145132e-20 +3bd66ee9916f38be 1.900170310609932e-20 +bbe9f755ec0a094f -4.3988377285490765e-20 +3bfec4b25943ae9d 1.042472124825956e-19 +3c0f4f1745c92e80 2.1215768983814835e-19 +3c18a55305631cdd 3.340128473636343e-19 +bc25501e037d4da4 -5.776888833741574e-19 +bc3ece69f5e25e3f -1.6700131039710345e-18 +bc47dd87ffdff543 -2.5874871297060224e-18 +3c50fed20da77a69 3.6852883287472994e-18 +3c62544732958edd 7.949028323193496e-18 +3c7df7e0cd48b280 2.5993334181799036e-17 +3c8ffcc84f6483e5 5.548934834187235e-17 +3c955582b1e213a2 7.401726972735359e-17 +bcad355b707b7f05 -2.0267417261326106e-16 +bcb138ab66d2e8bb -2.3899445452820685e-16 +bcc50fbddee603aa -5.845738045550756e-16 +3cd0085516f4652d 8.899852169272467e-16 +3ce08514402b66a7 1.8340707009327323e-15 +3cfc9447fdf279aa 6.345862393442977e-15 +3d00606b2e10b7d9 7.272687090836103e-15 +3d16ca96eb7640f6 2.024279886202151e-14 +bd28998b70c051f7 -4.3697994453456226e-14 +3d38dc6ac5234c94 8.832402962601679e-14 +bd46ac166ff2af0d -1.610957935297049e-13 +bd5b9de5b8412913 -3.9245814058292154e-13 +bd6c080b595b8f81 -7.967009643383329e-13 +bd7928139946f8de -1.4299842549136955e-12 +bd85cc2e358f82af -2.478097951183905e-12 +bd969e295685d66a -5.142696469991392e-12 +bdac6cb0111c0746 -1.2925994072556857e-11 +bdbcb7e0eb472ec8 -2.611911963178049e-11 +bdc476db3ba91295 -3.7224313438571714e-11 +3dd8a41dce2845f0 8.964372607012217e-11 +bde685d5a5ed697b -1.6387487457623863e-10 +bdfc58adc2018d69 -4.1249442924411184e-10 +be0623bbc02df433 -6.443466874289432e-10 +3e11bda48460c31b 1.0326499807269167e-9 +3e28bd058581a45d 2.879924781335018e-9 +3e3f897efac21055 7.34280184930355e-9 +3e4abdf8b7d32135 1.245274971541714e-8 +3e55acf0023c6a37 2.0186917150999168e-8 +3e65bb261e227f23 4.0477235578290355e-8 +be7630d8b35f931d -8.266719650447974e-8 +be86b382b1484b7b -1.6913721680218507e-7 +3e9ff6ce250d652d 4.7630195348020716e-7 +3ea4d37c7ff594a7 6.206666966322763e-7 +3eb67bcca467b64e 0.0000013401264752315196 +3ecd7488874f2aa2 0.0000035113344502892422 +3edcbd1718315c77 0.000006851824198759248 +3ee9c11c97c6c30f 0.00001228062750943803 +bef869335daa6d49 -0.000023280086547045474 +3f0bc6c58b854bd3 0.00005297937736039424 +3f1d202fe3b2d584 0.00011110584539405192 +3f2df901a82e512f 0.00022867341257593865 +bf3862032655a64d -0.00037205292604500646 +bf4995b67d3b3e41 -0.00078078661541698 +bf515528c83cf87d -0.001057901216838824 +bf6a178d74073ecd -0.003185058856821721 +bf71bf8dcf1923f0 -0.004333070699041194 +3f842b01eddfedb7 0.009847655364857084 +3f9b3604db11dfac 0.02657325350853028 +bfa7007703039713 -0.044925421827242705 +bfbbedd336f834fd -0.1090976723864578 +3fccb8149faf5bd4 0.22436769291991132 +bfd6dbce56fa7063 -0.35716589445087815 +3fe75abcc241fc43 0.7298263353440081 +3ff7eb150ac40f7b 1.4948931141416029 +4006e0a3637739a5 2.859686638904537 +c012174222671e96 -4.522713220161355 +4022dfc5b660b52b 9.437055301003587 +c03e3ad30e865e81 -30.22978297024247 +4044360c6624d27c 40.42225338743171 +40572a9a642384d8 92.66567328899998 +c0675ff1c333f5d1 -186.9982620253418 +c070f0bd1e06c67a -271.04617121359445 +c080675317a81576 -524.9155724650907 +c09d23f4e894d8dc -1864.9891684777604 +c0a5a74b33b2466d -2771.646878787133 +40bcbbad32cb19a1 7355.676556295163 +40c4bd7636f8c7d2 10618.923552606171 +40d717e379e482dd 23647.55431473522 +40edc2f6ef5c5c98 60951.716718846175 +40f4452eda44c221 83026.92828822929 +4102c053494ee3e5 153610.4107950024 +4118d8182edc2762 407046.04576169525 +c12d90b0223c07ac -968792.0668642423 +c13c78c664ca14a3 -1865926.3937085054 +41418d120886c84a 2300452.066613232 +c15ae3c3fa98975a -7048975.915563429 +c16b2aefa84b30f4 -14243709.259178616 +4171f5cb34e31b95 18832563.305446226 +c189100cc8fa98e2 -52560281.12236191 +c19461a396771553 -85485797.61629228 +c1a30f223146cfb9 -159879448.63830355 +c1bd5d59e5ac8c66 -492657125.6740173 +c1c9b4bfb5340cfe -862551914.4066465 +c1dc9318300c9b67 -1917608128.196985 +41ecaa7ee704bb91 3847485240.1478963 +41f3695fa1da76a6 5210765853.653967 +c20b01ff7146abcb -14499704360.833883 +421ad0e763cfb45c 28793493747.926132 +4228403ad006bc34 52078405635.367584 +c23103100b173c4c -73065827095.23553 +424df6f90b2f8134 257395136095.0094 +c25897cde4931336 -422504272460.3002 +42613af948d83890 592030877377.7676 +427fb96ed297ace1 2180080544122.805 +c28db97168b59bf8 -4085288605363.496 +c2955bb509e30ecf -5870905882819.702 +42a8e8a94cce9b12 13693775931213.535 +42b5e795fbdb82a4 24084397939586.64 +42c67b6f6fda1ca1 49438812779577.26 +42d2e99445a6a0d8 83177697090179.38 +c2e0e0df67acdb99 -148464054658780.78 +c2f4687e21fa446e -359024404964422.9 +43024c533d696b66 643808747138412.8 +c31f83e2ac45a030 -2217683463530508 +4324a92082932770 2907728314471352 +4336df63b9cddd3e 6438068899667262 +c34c71d72466c904 -16012936381043208 +c35c4d08c190c596 -31863997401863770 +c367b6b02faa419d -53398338356251880 +c37faa8e49ead25e -142612038083749340 +43845478d572507a 183116079617937200 +c3965ccce6f7ea5a -402847014766417540 +43a460859997d30e 734160186623231700 +c3bd2073f361ff28 -2098804915513010200 +c3cf9e981eb1f6d6 -4556851438196468700 +c3da3c237d5de0bb -7561699859766636000 +c3e12ebffbbc15ba -9905104283893617000 +43f42f996fdb4311 23272798565807755000 +4407eb0ae01811d7 55151463679747875000 +441c99aa998eb434 131895416383974740000 +c42c6a22312e245c -262078281627672120000 +4432c8c22466ef14 346507558304355700000 +c44011b8abe367ca -592849729333799200000 +445a77e5699e1385 1.953019094017127e+21 +4463ba11db7d6ee2 2.911167010383343e+21 +c478d6e453326cbf -7.33130321062198e+21 +c48e210a95d4c7a6 -1.7785062473773454e+22 +44977723e26847a9 2.770304434864158e+22 +44aca3e88d845d66 6.762491899984496e+22 +c4b95d752e9ccc5c -1.1978315312934902e+23 +c4c943a53ff162c4 -2.3861400285205192e+23 +44d4148ec78cb101 3.793062115417308e+23 +44ec19f83f49c823 1.0616425457564934e+24 +3af590af42a73373 1.1148933784351405e-24 +bb0f1f0bd02ac1d5 -3.217864311798877e-24 +bb18f3c4172c4116 -5.159996264663466e-24 +3b2249a2e4189c14 7.563591360488353e-24 +3b32f1e1e42d282c 1.567081517371435e-23 +bb49c3ab7b52f97c -4.262351739965806e-23 +3b5499f3a129838e 6.816422780455736e-23 +bb6be0f8da68f309 -1.8448640434743926e-22 +3b702d412edfc0af 2.1409784590893614e-22 +3b8fde448abbb4ee 8.435451135146682e-22 +bb991631abd03807 -1.328078597486021e-21 +bba06a1fc8e779b7 -1.7379578180588205e-21 +3bb0b71954963a84 3.539587688106997e-21 +3bc49372eb0337f1 8.714263212918027e-21 +bbde4a43735aedde -2.5656705657597352e-20 +3be0f519a1b6ef85 2.872699087659602e-20 +3bf3c3d0a48ac31a 6.696609407945571e-20 +bc07bacfdf28501c -1.60798934731642e-19 +3c139cab8ff987bc 2.6579206547408196e-19 +3c272c6e21161bb3 6.281204787766519e-19 +bc374ad50d2cbb44 -1.262678839922865e-18 +bc4286471d14b332 -2.008432765517577e-18 +bc5631e66dcc16d4 -4.81275659861108e-18 +bc6ec836273abbf9 -1.3349597604809413e-17 +3c77b982f33910ce 2.0577857464705442e-17 +bc80bd2f385c916f -2.903753933917094e-17 +3c9b16dbc43f938d 9.398485764685324e-17 +3caa12c5d1f6afea 1.8092007750445117e-16 +bcb289ad0269f835 -2.57263601593922e-16 +3cc0bd98f01f8685 4.646454026644526e-16 +3cd94941475eb8c8 1.4036634257848501e-15 +bce069a7aa1537b9 -1.8221773651631215e-15 +3cf8f82b61d7d396 5.544323213831083e-15 +3d01fe1d0bf059b1 7.990333158012157e-15 +bd15f6522c43f542 -1.9506344414504015e-14 +3d2a6021221e6228 4.685230971843022e-14 +bd3f0fe5825fd4ec -1.1035473258264864e-13 +bd4510b684b9d045 -1.496778524105876e-13 +3d535c39d88612a5 2.751258088096065e-13 +3d60be29676717c0 4.758595444409687e-13 +bd7fe4c85d4a29ef -1.8129459430333594e-12 +3d8c963196bf267c 3.249930860632325e-12 +bd9e4c8730f44730 -6.889180861985962e-12 +bdabc1084c1e63be -1.2621072918291764e-11 +bdbc33c8769b70f9 -2.5649822034531172e-11 +bdcfaa0b9e055858 -5.759691660470355e-11 +bdd122b02ab444e8 -6.233858800348486e-11 +3de3177343af1661 1.3890969090439542e-10 +bdfb1dbb4c3bf00d -3.945917587765583e-10 +be023868030424ad -5.302816016436879e-10 +3e174b45d9d6a7a4 1.3558912667580563e-9 +be20c9ef34ee63eb -1.954474283327818e-9 +3e3befe856ba25ec 6.5046220455325535e-9 +be4eb0b2396daaa7 -1.429124711428379e-8 +be548032c3453832 -1.909283416203715e-8 +be63dcfd2d0ffec2 -3.699816420593483e-8 +3e75974a480c11bf 8.043265787785931e-8 +be873ca85117865c -1.731287189574244e-7 +be9b3bd8c83fbe50 -4.0581489479851535e-7 +3ea6418e2bd2552c 6.632827404146939e-7 +beb69897fcb93a86 -0.00000134683067444836 +bec85b7288b82973 -0.000002903606462605399 +beddab6b8d4f65e1 -0.000007073786226074905 +bee73116a3e4a36a -0.000011058688980174296 +3efc696d02bef0ac 0.000027095622655557455 +3f09b88ce588264d 0.00004905872328115884 +bf1c417390010541 -0.00010778682553882082 +3f273885b9de770b 0.00017716057203370108 +bf38b703d99b6925 -0.0003771194838983075 +3f48d605b8b8b6b7 0.000757935327276824 +bf5967b59f068a1a -0.001550605167815028 +3f6ba24f67c5418e 0.0033732939611256845 +3f7deafc7171688d 0.007304178339007217 +bf8dd1abeae0091c -0.01456007299930611 +bf9bf58618080e0e -0.027303786484493435 +3fab6f1eacde5d92 0.05358215198719586 +bfb690b5e6f0c5a9 -0.08814560783573334 +bfc23f41f57d6e62 -0.14255547034086918 +bfda1ded1c7c4c3e -0.408076551263722 +3fe2f0bea69c7b26 0.5918877843977028 +3ffce973670b4a50 1.8069948220405827 +c0005422bbd5c8c4 -2.0410818743340275 +c01ceecf1662d255 -7.233211850911705 +4022937b0fd222b1 9.288048262025173 +4038bb425c7b0ab4 24.731481342353348 +c04e3f830415d1a1 -60.49618578973173 +405df904d9d9b242 119.89092108020216 +c066b8663302d1a6 -181.76247549582075 +407dc1329eee7d85 476.07485860023763 +c087ba324f80603e -759.2745656995173 +4099be2499fcaf7e 1647.5357436639893 +c0a22357a94b49a0 -2321.671213486421 +c0b4fc2d5399b733 -5372.177056891657 +40cfa798888f289d 16207.191667456465 +40d4316b1c5fb18c 20677.673606799493 +c0e2d730f064edef -38585.5293450019 +40ffeaab6aeabff4 130730.71360278112 +41055025fe78178b 174596.74925249474 +411abed5e3b86564 438197.47238310263 +c12297323396ccae -609177.1007598841 +41330f4c7eb90ab7 1249100.4950110146 +414fc73a03943562 4165236.0279604653 +c1531e221f220588 -5011592.486451514 +c1671525942a2383 -12101932.630143886 +417d231a7ff2f704 30552487.996817604 +418f829d4e8453ea 66081705.81461318 +c1928dfa9ceae1cd -77823655.22937699 +41affb7ec1cee74e 268287840.90410846 +41bd653b411d6900 493173569.1148834 +c1c7a2c8fa4113d2 -793088500.5084174 +41de33ed93612575 2026878541.5179112 +c1ee4fc2805e1a7f -4068348930.9407344 +c1f970d96281d98b -6829217320.115611 +42026e37faf3d8f8 9894821726.480942 +c21a7d95507d0149 -28444021791.251255 +c2240d5cd7a2890b -43061767121.26766 +c23e479a0a3ceed9 -130050296380.933 +424045402e400144 139762621568.0099 +4255cc619eb85046 374493051617.2543 +c266ff7a1b55f166 -790203783855.5437 +c279e70f023bc304 -1780011246524.1885 +4280a21901abf582 2286048785790.6885 +4290eae3223782e9 4650254765536.728 +42a0a424769677ac 9148586216251.836 +42b9088535953043 27524385314096.26 +42cce41f297c6ae3 63532201867477.77 +c2d495e250844af6 -90535918440747.84 +c2e7969f43af3fe3 -207485476370943.1 +c2f3142083a3f95c -335634652348309.75 +430be0be862d69ae 980866658839861.8 +431c3286ecb498fd 1984213606540863.2 +c32a2866824c6e8c -3681385066149702 +433056dafb20d551 4599098143462737 +4348073e5d3fe0bb 13526727749779830 +c358d7d5311ed1ba -27970840372659944 +c36738ea83cdc299 -52292034793182410 +43780791a351df5c 108219544524158400 +c385188a384f2be2 -190014606086077500 +c392e22ec0e30d35 -340175360849300800 +43a1dc4888d9266f 643491673058523000 +c3b46a50d48aefe3 -1471077102084154000 +c3c8f8e8cf9c62fc -3598888059028240400 +c3df6051bf366539 -9043587578532783000 +43ed27f4d21067b3 16807335473977334000 +43fdbc7a2423cbbc 34283549093751670000 +440e3ed267b8e32b 69741139603816280000 +c41cd8954170382d -133028821524456100000 +442fcda8d6c3731d 293334198496898480000 +c430317d7ca792bb -298714048718463240000 +44455a0e7b51135e 787741770498271100000 +c4591f5fdc78a6f8 -1.85371747916583e+21 +c464d374ba5ce9b5 -3.073375118573704e+21 +c47362fe63689d26 -5.721942168979955e+21 +448c5b95dd15c59c 1.6739464255309701e+22 +4497eb2d19430917 2.8238165916409007e+22 +44a1a9b7271b2ac5 4.170546375217221e+22 +c4b8b640f1670dd0 -1.1669878264508346e+23 +c4c818c5a76ab7cb -2.2758751983264943e+23 +c4d0ed9140b8f064 -3.197608345610404e+23 +44e5641f63011884 8.081330576022094e+23 +bafe784cc9fbbbb5 -1.5752580313833118e-24 +bb0a7d515af40182 -2.7389524447401826e-24 +3b1d7c405773c901 6.097428869635177e-24 +bb2ff85defa94ed3 -1.322255792439943e-23 +bb3242dc06e3c88e -1.5105285657707033e-23 +bb456f9fa633ec9f -3.546293652782555e-23 +3b52e641f11b4c86 6.253301364144602e-23 +bb65d6a76a849b9f -1.4451501796000982e-22 +3b79b5893cd49568 3.4025744061711327e-22 +3b84317d45a16642 5.345126702412649e-22 +3b9d398d13a7f40e 1.547148501543853e-21 +bba51f36ee307866 -2.2363715309283894e-21 +3bb3048882d2c116 4.027156312830227e-21 +3bcace70bd6de114 1.1352955291579417e-20 +3bd646f544e1ffd6 1.886950543958506e-20 +3bed0c6fbabf09a9 4.9210208418855835e-20 +3bfc2cb450c93dfa 9.545934734023355e-20 +bc09e8d10f9a9101 -1.7556919471427126e-19 +bc171cb391c7c463 -3.1322756634011044e-19 +bc27b36419cf332b -6.424100121019011e-19 +bc37ce974b4f078d -1.2905798427499846e-18 +bc4ac062e36ddac2 -2.9004044085250204e-18 +bc523c3ca56bf1f7 -3.954150459154942e-18 +3c6612078f4bbbd4 9.571522330198409e-18 +bc7e45ab968759a3 -2.6256904181402225e-17 +3c80378534e85fd0 2.813179606363703e-17 +bc9d4ee2f9b27b6f -1.0168307470137802e-16 +bca4cc9357002459 -1.4432290937081247e-16 +bcbf8167918e1251 -4.372264575534182e-16 +3cc8cb778b09ba35 6.881937473559832e-16 +bcdd740d99358401 -1.6349883944626633e-15 +bced2168e174cf49 -3.2341359148873543e-15 +3cf1f5e55d42d497 3.98803902601218e-15 +bd0e5f4eb42394fe -1.3488008342518183e-14 +3d1080bb0b9f2c57 1.465747886288165e-14 +3d2e7198b057e7cf 5.407893883248618e-14 +bd3c6d33dfb75bc0 -1.0099147396676296e-13 +bd4d3fa53448c577 -2.078239061133064e-13 +bd570361a6c5e93d -3.270373666877882e-13 +3d6dd27d728b8daf 8.47598661154468e-13 +bd730ea479d01174 -1.0832762428670387e-12 +3d86e08f9fa56c56 2.6008355601436556e-12 +bd97b5ca2257f30e -5.391056122886282e-12 +bda8f77f635e5f68 -1.135358049352527e-11 +3dbf56e6f24e39d6 2.8503074157944686e-11 +3dc199824b541025 3.201356663794093e-11 +3dd602cd3a481145 8.007534788923921e-11 +3de8baefd05e7027 1.7993604539035102e-10 +3df186a99c682507 2.5503723820910397e-10 +be0f5fbc78e3548d -9.131026922210171e-10 +be1a65ad709e1164 -1.5365179705568057e-9 +3e21eb86275bf446 2.086164403441976e-9 +3e322bf2a73c922e 4.230921935530284e-9 +be40d9cc5e07a08a -7.84675341452527e-9 +3e5ba62d29d4cd4b 2.575025580715859e-8 +3e66012b333f5611 4.098669706380587e-8 +be70811f59b48cfd -6.148362390440727e-8 +3e8bdb8b6ef37aa4 2.0755526672959328e-7 +be9d9470c106b551 -4.4077404573013383e-7 +beaf044f2f75cc7b -9.24373664652747e-7 +3eb227cc67915923 0.000001082149905925314 +bec2bc30968e5351 -0.0000022333999151837162 +3edb5b07e933bbb7 0.000006522080769884767 +beec4b9e7f79d100 -0.000013492292040264298 +3ef7f4df629762a2 0.000022846730791533717 +3f0f4b01dc10a4e7 0.00005968665528428303 +bf13105877c15c6e -0.00007272281612934344 +3f25577516580556 0.0001628237179574314 +3f3b17eb2b6720dc 0.00041341296619325034 +bf465fa673d8599d -0.0006827891117520583 +bf5470ca9ec62e6e -0.0012475947106364254 +bf60062f8cd9f9b2 -0.0019560745921005313 +bf73a94b170a66c1 -0.004800122566530341 +bf8920f6cbfde24c -0.012269905186017692 +3f981ccc704b0a88 0.023547357896641447 +3fa62790e6023e9d 0.04327061469768732 +bfb112027bd23da0 -0.06668105624179654 +bfc7a7ff2fa8f1db -0.18481435610909966 +3fd3d96f4800b6f8 0.3101461604263558 +bfeaeccb93480995 -0.8414056660260935 +3ffdf7236ed25cc6 1.8728365258191828 +400947ccc4fe09ff 3.1600585355784 +c017197977926a2c -5.774877422629782 +4021dea0a75245f8 8.934819439690713 +c03115bb44d49f73 -17.084888746175647 +c04a18f2818e8a8d -52.19490069828053 +c0590cafffce94d8 -100.19824214147536 +c06508aefeed3ff1 -168.27136179292532 +4078499fcc3d39ce 388.6015131370194 +c080d0ddbba8f03d -538.1082680891337 +409638afe9de0e9d 1422.1717905708713 +c0a722a46bddb5c0 -2961.3211354526866 +c0bb2d3e9f897eab -6957.2446218427895 +c0c9594b6210a696 -12978.588930207578 +40dfbd3ca099e25b 32500.947302313853 +c0e29eab8d55275f -38133.361002518555 +40f817045e4fbad1 98672.27302525497 +c104a97b518fb1e4 -169263.4148248575 +c11b31349df25787 -445517.1542447735 +c124bc96a77cb2e8 -679499.3271232517 +4139222e2059f18d 1647150.1263724298 +c14977405608812e -3337856.67213454 +4150d63873f91bac 4413665.812079351 +41670bacdb1b829b 12082534.847108176 +417c1dd2d2a2735e 29482285.164660804 +4185c8e9d1802038 45686074.18756145 +419daf643adc2e78 124508430.71502101 +c1a68ab5d0250824 -189094632.07232773 +c1b0775785abcb34 -276256645.6710694 +c1ce40849a70d1a2 -1015089460.8813975 +41d324e524461881 1284740241.0952456 +41e2e65c157894f0 2536693931.768181 +c1fe35e8525b59d9 -8109589797.709435 +420ce7f90d46b92a 15518867880.840412 +4210127304673ed8 17257251097.81137 +4222b6ec913146a5 40189184152.63798 +423563af76462d68 91866756678.17737 +424495648ae8fb32 176811480529.96246 +c252f97aaa2da644 -325979908278.5979 +426696d6b8c8ec08 776159479367.376 +c27b6d4ac5a37fd0 -1884763740727.9883 +428adf8ecd53cd2f 3693434481273.648 +c29b0b177f75c175 -7433613204848.364 +c2aafe9b9bbbd7b7 -14840417345003.857 +42b50fe8633fadd8 23158067511213.844 +42c0e0dbc0afa483 37115891113801.02 +42d97f50de43bb0a 112138433138412.16 +c2ec72b0e588befc -250231357457911.88 +c2f839bad0b19081 -426179623065864.06 +c304eba25ca4f3e0 -736072763219580 +431cdfcc2b534207 2031841835274369.8 +43204f3805138543 2295350824649377.5 +433f23a464429289 8764913241723529 +4341a48d45a795ec 9932002540071896 +c357e4c987fa21c7 -26901914740885276 +c36f151426cd4444 -69991204580368930 +c37fc05c8ac14fcc -142995647607667900 +438ccd5bd6d8be53 259426997700643420 +c39899ce374efbef -443168666322664400 +c3a5725f5c537dd8 -772701236141943800 +43bcb7f9c3c9ad23 2069397173097538300 +c3ccc79ddaf316b9 -4147599434268373500 +c3db9c92c2fc7ed4 -7958506006193590000 +c3efb83db1eeb329 -18285157162510010000 +43fe50956f5a840f 34950561996275250000 +c40683cd662ad2c7 -51915716143114410000 +c4108943814ed6a2 -76259699140399240000 +c42c96c3c833a38f -263686290457064080000 +4431686fb92ac708 321120086348800700000 +c446ae64d5045086 -836789545392776400000 +c450cff8a4e4b9a7 -1.2405352569668095e+21 +446a93bd099ff914 3.922088172746443e+21 +c47e3e4bec9c03e6 -8.926260221115453e+21 +4481b52dd8fb0261 1.0452799319054964e+22 +44993da2458c4c25 2.9799026591625904e+22 +c4a8b8d8106afdfd -5.837328277999248e+22 +c4b457350bc6f746 -9.605601875927972e+22 +c4c7cea998d46b28 -2.2485335827266315e+23 +c4d89f7c606609fe -4.651151606881715e+23 +c4e69d60b4c17a6d -8.543613587996768e+23 +baf9ab98ed09c3d9 -1.3271235193888315e-24 +3b0c0ee5aae4061e 2.9011490503941776e-24 +3b1d6a294b578992 6.0828158609553614e-24 +3b2089e739130201 6.840239559709235e-24 +3b3011dbdb6b8445 1.3292594750253286e-23 +bb4cf10a02aee20d -4.787979300010943e-23 +3b5ac89858409c89 8.861941456334386e-23 +bb61869b54a61ff0 -1.1597606651075088e-22 +3b7a593ad7e8afcf 3.487202103082225e-22 +3b89a2ea97356d50 6.785896478592164e-22 +3b96dc3885fe538a 1.210210895413644e-21 +3ba76d3e5a4a0dad 2.4804018029479644e-21 +3bb90cab502452f6 5.304435630022678e-21 +bbcb75fe4a6867d3 -1.1630148366116305e-20 +3bd635f467a723dc 1.8813245988674863e-20 +3be34dc4b7ed1a9b 3.270188030604682e-20 +bbfb81a16a4b066e -9.31952040584016e-20 +3c01e6964d57cbe2 1.2130007101003115e-19 +3c1eb3e574a9dcf5 4.1609944610958755e-19 +bc26c6188bb1120c -6.1728541216300555e-19 +3c32f58692f37328 1.027774040282574e-18 +bc445949f2f35fce -2.206219649525233e-18 +bc5e31a8a3da8af1 -6.547275632459334e-18 +bc60595b1229a3a4 -7.090268425498014e-18 +3c79f94b29d799e3 2.2528683045101055e-17 +3c894ff5657e2e10 4.390990731220621e-17 +3c928766b965564c 6.428507447523588e-17 +bca4c3fd984b8fe0 -1.4409021407377189e-16 +bcbce6dcc19ae0ff -4.01093130560786e-16 +bcced56e57420afe -8.558075059111267e-16 +3cd90ddaf8e7f16b 1.3907831831733706e-15 +3ce903ed82550449 2.7772609602523407e-15 +3cf1e3f3549b9c01 3.972473834049191e-15 +bd0cb8d9f1a78b79 -1.2755163841118531e-14 +3d169c72ae973a2f 2.008271318875968e-14 +bd2eaab1f6777f9b -5.4475140835971523e-14 +bd3f0a39c8ff17a4 -1.1027603445974006e-13 +bd47afff6749808b -1.6830974585675233e-13 +3d50647cd962514a 2.3295186291169047e-13 +bd6da4753cbd8768 -8.42488074645392e-13 +43de5b45dd41610a 8749675442421901000 +43b2bcdd397d4fd9 1350197227289565400 +437f19b17421a261 140063587620759060 +4341572a63771e83 9761828346150150 +4302fe4e97eb9eb5 668270386312150.6 +42e45c0b36f59746 179084461583546.2 +428d287a8824d2ec 4007461455002.365 +4261abc081f5716c 607167713195.5444 +42310439bade9885 73085336286.59578 +41f1482863f9739c 4639065663.590725 +41d2ba86143c718e 1256855632.9444308 +417be454f380811b 29246799.218873125 +416f7ef67fb64575 16512947.990999917 +413b9bbf91a4d48b 1809343.56892136 +4100c314b7a4a775 137314.58966952158 +40bf074dbd1e3ca0 7943.3036669633875 +40934544bac7e5c3 1233.3171187623964 +4064fb5010cad16b 167.85352363217257 +40085b34b1f3e7c0 3.0445341017302496 +3ffa4bdc145394a8 1.6435204309466744 +3fc2311d4381a119 0.14212384983262252 +3f71d18382eadcef 0.004350198475257402 +3f469d4130babb82 0.0006901329338314529 +3f2049feda708ea9 0.00012427555086091016 +3ed1d8e87594cf7c 0.000004255127202857394 +3ea5f5a52046652f 6.544456146339809e-7 +3e6c1d9ab42aff93 5.23694638951924e-8 +3e32c404c1054fd7 4.369229436872867e-9 +3e1b26dade1dd99b 1.580441437847724e-9 +3dd9717efa9306e8 9.256234549981111e-11 +43daa08096eadc87 7674699307734408000 +43b684636d0707b0 1622531085680750600 +4367019699650374 51805366356810660 +43302ab7e2db7f7d 4550568900788093 +4306d88dc10cfc0d 803819103559553.6 +42812cc3de5305a0 2360495295072.703 +42af25f06c4442db 17123903939105.428 +42714ffde0c1ece5 1189703715870.806 +424441b3aee99e56 174003281363.237 +420118bbfd8c0114 9178677169.500526 +41d4b684ad782a61 1390023349.8775866 +41a5389dbb43eee9 178015965.6326821 +416c21cccf7a85c0 14749286.483706355 +4137c20f57841bd3 1557007.3418595686 +40f0bc903a7706c9 68553.01427366877 +40c3637b25540d15 9926.962076670063 +408c57b4cd0e3071 906.9632817371877 +40660a010361703a 176.3126236823171 +402283f97145f5a1 9.257762470045067 +3fd53a998331df0f 0.33170163928979063 +3fc27f28da6fcfdd 0.14450560251100245 +3f7075b23e4a2023 0.00401849390312495 +3f53358c8a2cd4ad 0.0011724350412809878 +3f0f104fe9364062 0.000059249342622470056 +3ef17ced82f47148 0.00001667785562374624 +3ea4d8895fd9a892 6.212546279198044e-7 +3e72d3ad1c243036 7.013551964560008e-8 +3e429b88a7cb32ad 8.664817524421442e-9 +3e18f73512456d5f 1.4531922966048998e-9 +3ddc54e09109867b 1.0306958434245199e-10 +43d62db709e353fb 6392538783531658000 +437c30e98ddf7e3a 126961264218334110 +4368b298f1476040 55614155150524930 +4341cf8f4e4b10a3 10026578009792838 +430daf511c227acf 1044442152980313.9 +42e12a33c21c9e75 150983637263603.66 +429d3cc889cfdfb3 8036724929527.925 +4274be7ba849d94e 1425521935517.5815 +421b05a144b6c7d9 29014642989.695164 +4209117b20ef8f3c 13458433053.944939 +41d1056ee43dca86 1142274960.965486 +418b9e3f46bd8e95 57919464.84255711 +413f826ab898bee4 2065002.721080714 +413218a8be478ece 1185960.7432793858 +41020fc10926599b 147960.1294676781 +4093d57a6f1aab70 1269.3695644538057 +4099d59e813bd157 1653.404789862301 +4046585cfbe6379c 44.690337645922426 +40178e8b4c2cf48a 5.889203253024911 +3fd88e372eb93dad 0.38368014872409156 +3fba1b852f601102 0.1019824257528548 +3f8f7262fb5f8040 0.015354893966957772 +3f43431589cdeae8 0.0005878310363266167 +3f22b5cc317afc83 0.00014274709314141343 +3ecbb225479551f6 0.000003301606338249612 +3eafee53c4fd8286 9.516169345685073e-7 +3e2efd362b660001 3.6076069589888013e-9 +3e507a2d7d753853 1.5345641061086973e-8 +3e0c257b5d8916ca 8.191684510152896e-10 +3de6bf7843a389d5 1.655129660232011e-10 +43d8e70f4277fd5e 7177679018743134000 +4386669a34e059cf 201768383543982560 +437281246c9cdaac 83336688349063870 +4326255706cc706a 3116752597039157 +42fbb57d5b924d86 487460898940120.4 +42d84ab0ae647dbf 106836283462134.98 +42b01e1e321f6f8e 17721541664623.555 +42722518fbfdd7e4 1246908891101.4932 +4240f593fc257d06 145679185994.97675 +41ea5fcf86b22410 3539893301.566902 +41b338d17ecb1803 322490750.79333514 +41a0be7e25c3d303 140459794.88246927 +4169d2763a63edcc 13538225.824698351 +412e0b43be1a055f 984481.8712922743 +4105a6adf5cec36a 177365.74502327602 +40a7a60c610dc325 3027.0241779614175 +409cb07046dc9285 1836.1096453155926 +4066830c70080fd9 180.0952682645282 +402e82111b8309c2 15.25403676962458 +3fe362080fcd921e 0.6057167347576671 +3fbc52e01c286148 0.11063957869953811 +3f817a7905747bd8 0.008534379462217176 +3f4d9536393487c6 0.0009027971995332277 +3ea1aaf75cdd99c8 5.265425728595612e-7 +3ef1bc94990e01e1 0.000016914980338624228 +3eaabd0495f9f214 7.968649633062875e-7 +3e8565b746269f4c 1.5942251525607717e-7 +3e3049f74ea35fd7 3.792562023293993e-9 +3e1fa9db6f8086f7 1.8430585373917903e-9 +3dbe425065ca9a34 2.7520435897136994e-11 +43c494f40733cd86 2966156978165583000 +43b9575addaaeb04 1826028082023826400 +4340c711cc93c068 9444957775954128 +43203c810433b8a2 2285062223158353 +4311254634f4bc2c 1206514517618443 +42e295a0da9c132e 163470864867481.44 +429c16c0d6d66cf5 7721012540827.239 +42525218c5267e03 314747065497.96893 +4217d42417859eb7 25585845729.404995 +4200aea24bf9ffa6 8956168575.249828 +4192e9c1056af45f 79327297.35444783 +4188bde9a255249d 51887412.29157374 +415c256b54df6efe 7378349.326137302 +4126ba9343fe41c8 744777.6327992016 +40ec804c2106f165 58370.37903163097 +40bad8b9c46a0065 6872.7256532908405 +4096bcd3c43f5451 1455.206803311845 +4022ab39ca156d04 9.3344252730517 +40169716697faf8d 5.647546432894058 +3ffc1f825cedbb56 1.757692683222293 +3fb574a18626d210 0.08381089711367218 +3f90014ef7ec8b87 0.015629991418915103 +3f02cd65bb5a5a2d 0.00003586260519943363 +3eb46ab19f34d246 0.0000012169344899118553 +3ee848750b07b5b0 0.000011579053848155243 +3ea73757d37c3114 6.91896196310047e-7 +3e817863f2e53996 1.3016369266667824e-7 +3e3df245e4961434 6.97243469330652e-9 +3e00f0e8fd21e286 4.930495961722645e-10 +3de461b221e232a4 1.4829583476291872e-10 +43d898d49cb0533d 7089600891234088000 +43b0dd0d0fefe76f 1215141836560494300 +4383116373a7a9cf 171748588713753060 +434e8df5f5205ac6 17200673640723852 +431374299403d3eb 1368936620815610.8 +42d70d0b877b1004 101379181767744.06 +42a5594497a29f4c 11736573530447.648 +426cf9fb8ba91d98 995624770888.9248 +4244158f848afc78 172522146069.9724 +420920385ade8323 13489343323.814032 +41ccfd37cd532df4 972713882.6498399 +418edec26b4a403c 64739405.41125533 +41661281771ac025 11572235.72201545 +4102c370f5b74e80 153710.11997853592 +41017b2ac40b045e 143205.34572413837 +40c185d7b955f8c0 8971.685343500576 +408e36756e9252f5 966.8073398047987 +404d73813079c3d2 58.902380046325234 +40250bb58e7ab93f 10.522869541626845 +3ff77bb05db902da 1.4676974926989517 +3f71f708ed031210 0.0043859814324531815 +3f7b6c9933aeb899 0.00669536442266696 +3f5c88506145e338 0.0017414841614408882 +3f1960ed9a421f73 0.00009681177340514577 +3ebd89894f72ece3 0.0000017605573797003412 +3eb8e5a9cd936b98 0.0000014839841269628824 +3e77017c42780468 8.570329212283339e-8 +3e50df668c784cc4 1.5713887772657106e-8 +3e1bf658c50dedb6 1.6276196121934054e-9 +3de657c00e7bd42c 1.6256507879585963e-10 +43bd167629aee620 2095992597987598300 +4383a7ef238ad12a 177045442970395970 +43817daef2fcc72b 157544478672479580 +4318f0d73ade9e5c 1755051659339671 +431383dd5e2faeaa 1373252837174186.5 +42e332589195a58c 168855641828652.38 +42b01d449016218b 17717890389537.543 +4260dc3a2f011373 579314219016.6078 +4235eb69f6b19350 94143903409.57544 +41ecd475e43177e6 3869486881.545886 +41c4acb1eb504166 693724118.6269958 +4112df561c392fcc 309205.5275619 +416a5d1b597f6d86 13822170.796805155 +412dd1256eda30a9 977042.7165084082 +40fc11b7be6f7870 114971.48399302526 +40d1b1f5b9513ce5 18119.839435872524 +408f2915c228406b 997.1356242317828 +404bbde96358d93d 55.48368493879614 +4031e6c464c3d58e 17.90143422871693 +3febe8e2121b7af8 0.8721781114253124 +3fc57e63403422d4 0.16791954646057283 +3f5be5bf12e3f99b 0.0017027250246993478 +3f3ecc50a52a6839 0.00046994179607795814 +3f235b93da3d41d4 0.00014768771972707587 +3ec825f356778de8 0.0000028786950456592498 +3e979da4524aff76 3.519026725955148e-7 +3e864468bf976ae3 1.6590374211821178e-7 +3e088b8aa905042a 7.143559789181318e-10 +3e167f05874c81e3 1.3094499071818787e-9 +3dd8f132e998caf6 9.073913356736814e-11 +43e79c7745377a28 13610927187251250000 +439ddb15b447ad82 537834277760098400 +436ad557e614a471 60423383147684744 +43442f3470954f38 11362803615702640 +43171bdc6371115c 1626139459863639 +42b5d3eee0117412 23999989944692.07 +42930bf75b5ec614 5235528882097.52 +4268821968e9e30e 842095347535.0955 +4242f108c197981c 162706588463.18835 +41f351d862629ef1 5186094630.163804 +41d1627a9d178bc4 1166666356.3679056 +418870ff30665c64 51257318.04998091 +41455217d7ebbd4f 2794543.686881698 +412833a0919a0961 793040.2843783313 +40f41c109a93b240 82369.03773851041 +40d0cd46c63ac859 17205.105848975803 +40945f3582b79b25 1303.8022564590876 +405aca78964f9039 107.16361005563603 +401479f332ac6bce 5.119091789029836 +3fb0d15b5dcc3e48 0.06569453277574511 +3fc5159e6da44012 0.16472225526422385 +3f85edb88edd654c 0.010707322940720097 +3f4473509f4a8e79 0.0006240981734503957 +3eccf666a481137c 0.000003452599489891409 +3ed3610dfa9487f5 0.000004620342147350691 +3e9d9ce8361051ca 4.4126686843504004e-7 +3e6fb36262bb017a 5.9047192327909146e-8 +3e4a68152039d8ce 1.2296518476394692e-8 +3df33e408dead55b 2.800250152567071e-10 +3dc19dc59ba84f22 3.204385668023455e-11 +43e80c12d3c7aded 13862245259426097000 +43b2d222cad49ab3 1356184692337587000 +43771ceedb888c97 104091786804775280 +431be8405d255c13 1963796877367044.8 +42ff85e64ce9063a 554559278583907.6 +42c2c0dce6f28ed1 41239098287389.63 +429d9222ce8f5e6c 8128371598295.605 +4274b3b53c3c38b7 1422629520323.5447 +420b048ada0f1287 14505040705.884047 +41f0a7b350d81ad6 4470813965.506552 +41d923c6683e78cc 1687099808.9761229 +418d2d1570d9bba5 61186734.106314935 +416667361947ef96 11745712.790031236 +41216424a1c7ab0f 569874.3159764725 +4105811e6f948810 176163.8044825201 +40adf461191668e2 3834.1896445277243 +409b79c54c0f8b90 1758.4426729611332 +402cc1f7b4b09dcb 14.378842970428101 +3fede04d01bdc56c 0.9336304697045938 +3ff2e86b56b5216f 1.1817429911626183 +3fbc396d0f403ef3 0.11025125143373397 +3f68fd202e10ef34 0.0030503872408455205 +3f3b6979d124a875 0.0004182741551114263 +3f25afdabedfba77 0.00016545815691367305 +3eef5feec5c8055b 0.000014960640492669532 +3eb45c769dde122c 0.0000012136211959566793 +3e84d99f40f7b246 1.5534524820617967e-7 +3e417e6944ac0ee9 8.146182524984627e-9 +3dfa8730fea5ade2 3.8603453649600814e-10 +3dde695a2683c655 1.1063650831301246e-10 +43e2481f9e2aa0a2 10538701241490674000 +43a23ff3c887a798 657518829405195300 +43259e4646a38e4e 3042499590473511 +4341668e0785f2bc 9795669603378552 +4307937c5885dd87 829510841842608.9 +42b4943fbc128a80 22626957005450.5 +42ab2acdc2bf0d4b 14935327334278.646 +42548470a4408738 352485282050.1128 +423888d475f98571 105374840313.52126 +41ec21115b23f010 3775433433.1230545 +41c30b91e2d900b5 639050693.6953341 +419ac2fc0e89c04e 112246531.63452265 +41698a5623793a5f 13390513.108548341 +40f09d3a29554fdf 68051.63509112548 +40ef1ec77ad17810 63734.233742460725 +40d0b664fb306f25 17113.577831371822 +409abf1183b6b0a4 1711.767104010135 +4049651b5073eac2 50.78989606533061 +4023ab66daaa7356 9.834769089991521 +3ff40313f06b80a9 1.2507514373210518 +3f9fcdf2eebe4210 0.031059070417145007 +3f8f31a09bedaf78 0.015231375464018745 +3f58451bdf9ee6a1 0.001481320591194914 +3f00d8ea7a847ccf 0.00003213372775889291 +3e9afa1aca5f720c 4.019881977271513e-7 +3e9beb309a75c97d 4.1602120367310544e-7 +3e817d5a7931ef49 1.3030813459160904e-7 +3e517a5027bf1fd9 1.6277456257683937e-8 +3e1c423134e894b0 1.6448648724622053e-9 +3de73ba7342f30f4 1.690424693384741e-10 diff --git a/wai/wai-quantum/Cargo.lock b/wai/wai-quantum/Cargo.lock index 541174291..662dd7c4b 100644 --- a/wai/wai-quantum/Cargo.lock +++ b/wai/wai-quantum/Cargo.lock @@ -1079,7 +1079,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wai-quantum" -version = "0.6.3" +version = "0.6.4" dependencies = [ "blake3", "block2", diff --git a/wai/wai-quantum/Cargo.toml b/wai/wai-quantum/Cargo.toml index d0d02fff3..425891c35 100644 --- a/wai/wai-quantum/Cargo.toml +++ b/wai/wai-quantum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wai-quantum" -version = "0.6.3" +version = "0.6.4" edition = "2024" rust-version = "1.97" license = "Apache-2.0" @@ -178,6 +178,8 @@ quantum_fabric_fpga = ["quantum_fabric"] # The neural-processor member: exact fp16 limb products on the platform's neural processor. quantum_fabric_npu = ["quantum_fabric", "dep:objc2", "dep:block2", "dep:objc2-foundation", "dep:objc2-core-ml"] quantum_fabric_metal = ["quantum_fabric", "dep:objc2", "dep:objc2-foundation", "dep:objc2-metal"] +# The dense state vector in single precision on the platform's GPU (macOS). +quantum_sv_metal = ["quantum_sv", "dep:objc2", "dep:objc2-foundation", "dep:objc2-metal"] # The npu-cores member: the gate's arithmetic on a neural processor's compute core, programmed directly (Linux): the program built by the tile array's open toolchain, the host side compiled against the device runtime, both at run time. quantum_fabric_npu_cores = ["quantum_fabric"] # The tensor-engine member: the gate's products as exact bf16 limb convolutions on a tensor processor's matrix engine, through its kernel toolchain's Python run as a helper. diff --git a/wai/wai-quantum/src/lib.rs b/wai/wai-quantum/src/lib.rs index 89275ce5f..b62721ac3 100644 --- a/wai/wai-quantum/src/lib.rs +++ b/wai/wai-quantum/src/lib.rs @@ -62,7 +62,8 @@ pub mod gates; feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", - feature = "quantum_tdvp" + feature = "quantum_tdvp", + feature = "quantum_tableau" ))] mod repro; @@ -250,6 +251,9 @@ pub mod quantum_receipt; pub mod quantum_stabilizer; #[cfg(feature = "quantum_tableau")] pub mod quantum_tableau; + +#[cfg(feature = "quantum_sv_metal")] +pub mod quantum_sv_metal; #[cfg(feature = "quantum_mpsim")] pub mod quantum_mpsim; diff --git a/wai/wai-quantum/src/quantum_noisy.rs b/wai/wai-quantum/src/quantum_noisy.rs index 91211cb0e..e55c97e1b 100644 --- a/wai/wai-quantum/src/quantum_noisy.rs +++ b/wai/wai-quantum/src/quantum_noisy.rs @@ -731,9 +731,13 @@ pub fn linear_xeb(ideal: &StateVector, samples: &[u64]) -> Estimate { // The exact density matrix // --------------------------------------------------------------------------- -/// Largest register the exact density matrix accepts. +/// Largest register the exact density matrix accepts in [`DensityMatrix::run`]. pub const MAX_DENSITY_QUBITS: u32 = 12; +/// Largest register [`DensityMatrix::evolve`] accepts: `ρ` is then a state on +/// 32 qubits, 64 GiB. +pub const MAX_EVOLVE_QUBITS: u32 = 16; + /// `ρ` on `n` qubits, stored as a state on `2n` qubits: entry `(r, c)` at /// index `r·2ⁿ + c`. #[derive(Clone, Debug, PartialEq)] @@ -821,6 +825,50 @@ impl DensityMatrix { Ok(rho) } + /// Run `gates` under `noise` from `|0…0⟩⟨0…0|`, exactly, with each gate + /// and its noise as one superoperator on `ρ`'s vector: the gate on the + /// rows, its conjugate on the columns, and each channel's + /// `Σ_i w_i K_i ⊗ K_i*` on its qubits' row and column bits, in the order + /// [`run`](Self::run) applies them. They are fused (to at most four of the + /// vector's qubits for gates on one or two) and applied in the state + /// vector's tiled passes, so a gate costs one pass and no copy of `ρ`. + /// The result is `run`'s to rounding, on up to [`MAX_EVOLVE_QUBITS`]. + pub fn evolve(gates: &[Gate], n: u32, noise: &NoiseModel) -> Result { + if n > MAX_EVOLVE_QUBITS { + return Err(SvError::TooLarge); + } + let mut list = Vec::with_capacity(gates.len() * 4); + for (i, g) in gates.iter().enumerate() { + if g.qubits.iter().any(|&q| q >= n) { + return Err(SvError::BadGate(i)); + } + list.push(g.clone()); + list.push(conj(g, n)); + let k = g.qubits.len(); + if k == 2 && noise.depol2 > 0.0 { + let (a, b) = (g.qubits[0], g.qubits[1]); + let terms: Vec<(f64, Gate)> = (0..16u8).map(|j| (if j == 0 { 1.0 - noise.depol2 } else { noise.depol2 / 15.0 }, two_qubit(&pauli(a, j >> 2), &pauli(b, j & 3)))).collect(); + list.push(superoperator(n, &terms)); + } + for &q in &g.qubits { + if k != 2 && noise.depol1 > 0.0 { + let terms: Vec<(f64, Gate)> = (0..4u8).map(|j| (if j == 0 { 1.0 - noise.depol1 } else { noise.depol1 / 3.0 }, pauli(q, j))).collect(); + list.push(superoperator(n, &terms)); + } + if noise.dephasing > 0.0 { + list.push(superoperator(n, &[(1.0 - noise.dephasing, pauli(q, 0)), (noise.dephasing, pauli(q, 3))])); + } + if noise.damping > 0.0 { + let [k0, k1] = damping_kraus(q, noise.damping); + list.push(superoperator(n, &[(1.0, k0), (1.0, k1)])); + } + } + } + let mut vec = StateVector::zero(2 * n)?; + vec.run(&list, 4)?; + Ok(DensityMatrix { n, vec }) + } + /// Entry `ρ_{r,c}`. pub fn entry(&self, r: usize, c: usize) -> C { self.vec.amplitude((r << self.n) | c) @@ -888,6 +936,41 @@ impl DensityMatrix { } } +/// A channel `ρ → Σ_i w_i K_i ρ K_i†` on `k` qubits as a gate on the vector +/// of `ρ`: `Σ_i w_i K_i ⊗ K_i*` on the qubits' row bits then their column +/// bits (`q` and `q + n`). +fn superoperator(n: u32, terms: &[(f64, Gate)]) -> Gate { + let qs = &terms[0].1.qubits; + let k = qs.len(); + let d = 1usize << k; + let big = d * d; + let mut m = vec![complex(0.0, 0.0); big * big]; + for (w, kr) in terms { + debug_assert_eq!(&kr.qubits, qs); + for r in 0..d { + for c in 0..d { + for r2 in 0..d { + let a = kr.matrix[r * d + r2]; + if a.re == 0.0 && a.im == 0.0 { + continue; + } + for c2 in 0..d { + let b = kr.matrix[c * d + c2]; + // a · conj(b) · w + let (pr, pi) = (a.re * b.re + a.im * b.im, a.im * b.re - a.re * b.im); + let e = &mut m[((r << k) | c) * big + ((r2 << k) | c2)]; + e.re += w * pr; + e.im += w * pi; + } + } + } + } + } + let mut qubits = qs.clone(); + qubits.extend(qs.iter().map(|&q| q + n)); + Gate { qubits, matrix: m } +} + /// The tensor product `a ⊗ b` of two one-qubit gates on different qubits. fn two_qubit(a: &Gate, b: &Gate) -> Gate { let mut m = vec![complex(0.0, 0.0); 16]; @@ -1017,6 +1100,68 @@ mod tests { assert_eq!(a, b); } + #[test] + fn evolving_by_superoperators_is_the_density_matrix() { + for (n, seed) in [(3u32, 1u64), (4, 2), (6, 3)] { + let mut g = random_circuit(n, 4, seed); + g.push(Gate::cx(0, n - 1)); + g.push(Gate { qubits: vec![1, 0, n - 1], matrix: { + // A Toffoli on three qubits: dense, with its channels per qubit. + let mut m = vec![complex(0.0, 0.0); 64]; + for i in 0..8 { + m[i * 8 + if i >= 6 { i ^ 1 } else { i }] = complex(1.0, 0.0); + } + m + } }); + let noise = NoiseModel { depol1: 0.02, depol2: 0.04, damping: 0.03, dephasing: 0.01, readout: 0.0 }; + let want = DensityMatrix::run(&g, n, &noise).unwrap(); + let got = DensityMatrix::evolve(&g, n, &noise).unwrap(); + let worst = (0..1usize << n).flat_map(|r| (0..1usize << n).map(move |c| (r, c))).map(|(r, c)| { + let (a, b) = (got.entry(r, c), want.entry(r, c)); + (a.re - b.re).abs().max((a.im - b.im).abs()) + }).fold(0.0, f64::max); + assert!(worst < 1e-12, "n {n}: {worst}"); + assert!((got.trace() - 1.0).abs() < 1e-12); + } + } + + /// Times; run with `--ignored --nocapture`. + #[test] + #[ignore] + fn density_timings() { + let noise = NoiseModel { depol1: 0.01, depol2: 0.03, damping: 0.02, dephasing: 0.01, readout: 0.0 }; + for n in [10u32, 12, 14, 15] { + let g = random_circuit(n, 6, 2); + let t0 = std::time::Instant::now(); + let rho = DensityMatrix::evolve(&g, n, &noise).unwrap(); + let te = t0.elapsed(); + let old = if n <= MAX_DENSITY_QUBITS { + let t1 = std::time::Instant::now(); + DensityMatrix::run(&g, n, &noise).unwrap(); + format!("{:?}", t1.elapsed()) + } else { + "-".into() + }; + println!("n={n}: {} gates; evolve {te:?}, run {old}; trace {:.12}", g.len(), rho.trace()); + } + } + + /// Past the referee's size, against branching trajectories. + #[test] + fn a_fourteen_qubit_density_matrix_agrees_with_trajectories() { + let n = 14; + let g = random_circuit(n, 3, 21); + let noise = NoiseModel { depol1: 0.01, depol2: 0.03, damping: 0.02, dephasing: 0.01, readout: 0.0 }; + let rho = DensityMatrix::evolve(&g, n, &noise).unwrap(); + assert!((rho.trace() - 1.0).abs() < 1e-10); + let obs = observables(n); + let tr = branching_trajectories(&g, n, &noise, &obs, 4000, 3, 8, 8).unwrap(); + for (k, o) in obs.iter().enumerate() { + let (exact, e) = (rho.expectation(o), tr.expectations[k]); + assert!((e.mean - exact).abs() < 4.0 * e.stderr + 1e-9, "observable {k}: {} ± {} vs {exact}", e.mean, e.stderr); + } + } + #[test] fn the_binomial_sampler_has_the_binomial_moments() { for (c, p, draws) in [(1000u64, 0.003f64, 20_000usize), (50, 0.7, 20_000), (6, 0.2, 20_000), (1_000_000, 1e-4, 4_000)] { diff --git a/wai/wai-quantum/src/quantum_spd.rs b/wai/wai-quantum/src/quantum_spd.rs index 79317a633..14b3165e0 100644 --- a/wai/wai-quantum/src/quantum_spd.rs +++ b/wai/wai-quantum/src/quantum_spd.rs @@ -822,6 +822,22 @@ pub fn expectation_threaded( cfg: &SpdConfig, threads: usize, ) -> Result { + expectation_capped(circuit, observable, ones, cfg, threads, usize::MAX).map(|(r, _)| r) +} + +/// [`expectation_threaded`] holding at most `max_terms` terms: whenever a +/// rotation leaves more, the threshold is doubled and the sum truncated again +/// until it fits, and every coefficient dropped that way is added to the +/// bound like any other. A run that never reaches the cap is the uncapped +/// run, bit for bit. Returns the result and the threshold the run ended at. +pub fn expectation_capped( + circuit: &RotCircuit, + observable: &[(f64, PauliString)], + ones: &[u32], + cfg: &SpdConfig, + threads: usize, + max_terms: usize, +) -> Result<(SpdResult, f64), SpdError> { circuit.validate()?; for (_, s) in observable { check_string(s, circuit.n)?; @@ -832,11 +848,11 @@ pub fn expectation_threaded( } } Ok(match circuit.n.div_ceil(64) { - 1 => run::<1>(circuit, observable, ones, cfg, threads), - 2 => run::<2>(circuit, observable, ones, cfg, threads), - 3 | 4 => run::<4>(circuit, observable, ones, cfg, threads), - 5..=8 => run::<8>(circuit, observable, ones, cfg, threads), - _ => run::<16>(circuit, observable, ones, cfg, threads), + 1 => run::<1>(circuit, observable, ones, cfg, threads, max_terms), + 2 => run::<2>(circuit, observable, ones, cfg, threads, max_terms), + 3 | 4 => run::<4>(circuit, observable, ones, cfg, threads, max_terms), + 5..=8 => run::<8>(circuit, observable, ones, cfg, threads, max_terms), + _ => run::<16>(circuit, observable, ones, cfg, threads, max_terms), }) } @@ -851,11 +867,12 @@ fn run( ones: &[u32], cfg: &SpdConfig, threads: usize, -) -> SpdResult { - crate::pool::scoped(threads, |pool| propagate::(circuit, observable, ones, cfg, pool)) + max_terms: usize, +) -> (SpdResult, f64) { + crate::pool::scoped(threads, |pool| propagate::(circuit, observable, ones, cfg, pool, max_terms)) } -fn propagate(circuit: &RotCircuit, observable: &[(f64, PauliString)], ones: &[u32], cfg: &SpdConfig, pool: &Pool) -> SpdResult { +fn propagate(circuit: &RotCircuit, observable: &[(f64, PauliString)], ones: &[u32], cfg: &SpdConfig, pool: &Pool, max_terms: usize) -> (SpdResult, f64) { let mut t = Terms::::new(); for (c, s) in observable { t.add(Key::from_string(s), *c, pool); @@ -868,6 +885,7 @@ fn propagate(circuit: &RotCircuit, observable: &[(f64, PauliStri // before the next rotation and at the end. A Clifford gate keeps the // number of terms, so the work count and the peak are as gate by gate. let mut batch: Vec> = Vec::new(); + let mut threshold = cfg.threshold; for op in circuit.ops.iter().rev() { work += t.live() as u64; match op { @@ -878,7 +896,12 @@ fn propagate(circuit: &RotCircuit, observable: &[(f64, PauliStri } let (s, c) = sin_cos(*theta); rotate(&mut t, &Key::from_string(axis), s, c, &mut pending, pool); - bound += t.truncate(cfg.threshold, cfg.max_weight, pool); + bound += t.truncate(threshold, cfg.max_weight, pool); + // Over the cap: a doubled threshold until the sum fits. + while t.live() > max_terms { + threshold = if threshold > 0.0 { threshold * 2.0 } else { f64::MIN_POSITIVE }; + bound += t.truncate(threshold, cfg.max_weight, pool); + } } Op::QuarterRot { axis, k } => batch.push(Clifford::Quarter(Key::from_string(axis), *k)), other => batch.push(Clifford::Gate(other)), @@ -897,7 +920,7 @@ fn propagate(circuit: &RotCircuit, observable: &[(f64, PauliStri value += if flips & 1 == 1 { -t.coef[i] } else { t.coef[i] }; } } - SpdResult { value, truncation_bound: bound, peak_terms: peak, final_terms: t.live(), term_updates: work } + (SpdResult { value, truncation_bound: bound, peak_terms: peak, final_terms: t.live(), term_updates: work }, threshold) } // --------------------------------------------------------------------------- @@ -1458,6 +1481,30 @@ mod tests { assert_eq!((one.value.to_bits(), one.truncation_bound.to_bits(), one.peak_terms), (many.value.to_bits(), many.truncation_bound.to_bits(), many.peak_terms)); } + #[test] + fn a_term_cap_raises_the_threshold_and_the_bound_still_holds() { + let ki = kicked_ising(127, &heavy_hex_127(), 0.7, 5); + let obs = [(1.0, vec![(62, Pauli::Z)])]; + let cfg = SpdConfig { threshold: 1e-5, max_weight: None }; + let (free, t0) = expectation_capped(&ki, &obs, &[], &cfg, 4, usize::MAX).unwrap(); + assert_eq!(t0, 1e-5); + // A cap above the peak changes nothing. + let (same, t1) = expectation_capped(&ki, &obs, &[], &cfg, 4, free.peak_terms + 1).unwrap(); + assert_eq!((same.value.to_bits(), same.truncation_bound.to_bits(), t1), (free.value.to_bits(), free.truncation_bound.to_bits(), 1e-5)); + // A cap a tenth of the peak raises the threshold; the capped value + // stays within its own bound of the far more exact one. + let (capped, t2) = expectation_capped(&ki, &obs, &[], &cfg, 4, free.peak_terms / 10).unwrap(); + assert!(t2 > 1e-5 && capped.peak_terms <= free.peak_terms); + assert!((capped.value - free.value).abs() <= capped.truncation_bound + free.truncation_bound + 1e-12); + // A small register, exact reference: the bound covers the true error. + let mut rng = Rng(5); + let c = random_circuit(&mut rng, 6, 60); + let o = vec![(1.0, vec![(0u32, Pauli::Z), (3, Pauli::X)])]; + let exact = dense_expect(&c, &o[0].1, &[]); + let (r, _) = expectation_capped(&c, &o, &[], &SpdConfig { threshold: 0.0, max_weight: None }, 2, 40).unwrap(); + assert!((r.value - exact).abs() <= r.truncation_bound + 1e-12, "{} vs {exact}, bound {}", r.value, r.truncation_bound); + } + #[test] fn word_width_does_not_change_the_answer() { // The same circuit, placed in a 30-qubit and a 900-qubit register, is diff --git a/wai/wai-quantum/src/quantum_sv.rs b/wai/wai-quantum/src/quantum_sv.rs index 495bafa2d..1e2b0741c 100644 --- a/wai/wai-quantum/src/quantum_sv.rs +++ b/wai/wai-quantum/src/quantum_sv.rs @@ -134,7 +134,7 @@ impl Schedule { /// One pass of a run: a gate applied to the whole state, or a group of gates /// applied tile by tile, with the local bits their dense gates act on. #[derive(Clone, Debug, PartialEq, Eq)] -enum Stage { +pub(crate) enum Stage { Direct(usize), Tiled { gates: Vec, local: Vec }, } @@ -144,7 +144,7 @@ enum Stage { /// `tile − low`. Diagonal gates join any pass. A gate on more bits than a tile /// holds gets a pass of its own. The order of the gates never changes, so /// neither does any amplitude's arithmetic. -fn plan(list: &[Gate], n: u32, tile: u32, low: u32) -> Vec { +pub(crate) fn plan(list: &[Gate], n: u32, tile: u32, low: u32) -> Vec { let room = (tile - low) as usize; let mut out = Vec::new(); let (mut gates, mut local): (Vec, Vec) = (Vec::new(), Vec::new()); @@ -290,6 +290,7 @@ thread_local! { /// Run `f` with this thread's state-vector kernels on this thread alone. For /// callers that run many small states in parallel; the result is the same /// bits either way. +#[cfg_attr(not(feature = "quantum_noisy"), allow(dead_code))] pub(crate) fn on_one_thread(f: impl FnOnce() -> R) -> R { let before = ONE_THREAD.with(|o| o.replace(true)); let r = f(); @@ -369,9 +370,81 @@ fn kernel_lanes(re: &mut [f64], im: &mut [f64], m: &[C], offsets: &[usize], sort } } +/// A gate on `log₂ D` qubits, `L` groups side by side: each group's members +/// are loaded, from one run of `L` neighbours where the lowest target stride +/// allows, gathered otherwise, and every output is the [`row_dot`] sum, term +/// for term, over the matrix split into its real and imaginary parts. The +/// sizes are compile-time constants, so the loops unroll and the members stay +/// in registers; the result is the scalar path's, bit for bit. +fn kernel_fixed(re: &mut [f64], im: &mut [f64], m: &[C], offsets: &[usize], sorted: &[u32]) { + let mut mr = [[0.0f64; D]; D]; + let mut mi = [[0.0f64; D]; D]; + for r in 0..D { + for c in 0..D { + mr[r][c] = m[r * D + c].re; + mi[r][c] = m[r * D + c].im; + } + } + let mut off = [0usize; D]; + off.copy_from_slice(offsets); + let top = off.iter().copied().max().unwrap_or(0); + let len = re.len(); + assert!(im.len() == len); + let k = D.trailing_zeros() as usize; + let groups = len >> k; + let run = 1usize << sorted[0]; + let mut t = 0; + while t < groups { + let lanes = L.min(groups - t); + let mut bases = [0usize; L]; + if run >= L && lanes == L { + let b0 = spread(t, sorted); + for (l, b) in bases.iter_mut().enumerate() { + *b = b0 + l; + } + } else { + for (l, b) in bases.iter_mut().enumerate().take(lanes) { + *b = spread(t + l, sorted); + } + } + // Every index read or written below is at most this. + for &b in &bases[..lanes] { + assert!(b + top < len); + } + let (mut vr, mut vi) = ([[0.0f64; L]; D], [[0.0f64; L]; D]); + for j in 0..D { + for l in 0..lanes { + // SAFETY: `bases[l] + off[j] ≤ bases[l] + top < len`, checked above. + unsafe { + vr[j][l] = *re.get_unchecked(bases[l] + off[j]); + vi[j][l] = *im.get_unchecked(bases[l] + off[j]); + } + } + } + for r in 0..D { + let (mut sr, mut si) = ([0.0f64; L], [0.0f64; L]); + for c in 0..D { + let (a, b) = (mr[r][c], mi[r][c]); + for l in 0..L { + sr[l] += a * vr[c][l] - b * vi[c][l]; + si[l] += a * vi[c][l] + b * vr[c][l]; + } + } + for l in 0..lanes { + // SAFETY: as for the loads. + unsafe { + *re.get_unchecked_mut(bases[l] + off[r]) = sr[l]; + *im.get_unchecked_mut(bases[l] + off[r]) = si[l]; + } + } + } + t += lanes; + } +} + /// Apply a gate to a slice holding whole groups (block-aligned). One-qubit -/// gates run over the two halves of each block side by side; wider gates take -/// [`kernel_lanes`] when their groups come in runs, unrolled loops otherwise. +/// gates run over the two halves of each block side by side; gates on two to +/// five qubits take [`kernel_fixed`]; wider ones the general loop. fn kernel_block(re: &mut [f64], im: &mut [f64], m: &[C], offsets: &[usize], sorted: &[u32]) { let k = sorted.len(); if k == 1 { @@ -391,6 +464,13 @@ fn kernel_block(re: &mut [f64], im: &mut [f64], m: &[C], offsets: &[usize], sort } return; } + match k { + 2 => return kernel_fixed::<4, 4>(re, im, m, offsets, sorted), + 3 => return kernel_fixed::<8, 4>(re, im, m, offsets, sorted), + 4 => return kernel_fixed::<16, 2>(re, im, m, offsets, sorted), + 5 => return kernel_fixed::<32, 2>(re, im, m, offsets, sorted), + _ => {} + } if 1usize << sorted[0] >= LANES { kernel_lanes(re, im, m, offsets, sorted); return; diff --git a/wai/wai-quantum/src/quantum_sv_metal.rs b/wai/wai-quantum/src/quantum_sv_metal.rs new file mode 100644 index 000000000..857818a6e --- /dev/null +++ b/wai/wai-quantum/src/quantum_sv_metal.rs @@ -0,0 +1,1030 @@ +//! The dense state vector on the platform's GPU, in single precision — +//! `wai.quantum.sv.metal`. +//! +//! [`crate::quantum_sv`] holds `2ⁿ` double-precision amplitudes in host +//! memory, 16 bytes each. On a machine whose GPU shares the host's memory, +//! the same state in single precision takes half the bytes, so one more qubit +//! fits, and the GPU's memory bandwidth carries each pass. This module is that +//! state vector: +//! +//! - **Layout.** One buffer the GPU and the host share, `2ⁿ` complex values of +//! two `f32`s each, qubit 0 the most significant bit, as in `quantum_sv`. +//! - **Gates.** Gates are fused to `width` qubits by [`crate::quantum_sv::fuse`] +//! (at most [`MAX_WIDTH`]). Each fused gate is one dispatch: a thread per +//! amplitude group for dense gates, four amplitudes a thread for diagonal +//! ones. Every dispatch of a run is encoded into one command buffer, and +//! the device runs them in order. +//! - **Arithmetic.** Each output is the matrix row against the group, summed +//! in a fixed order in `f32`. The kernels are compiled with the platform's +//! safe math mode, so the device does not reassociate or approximate, and a +//! run is the same bits on the same device and operating system. +//! - **Tiles.** Consecutive gates are grouped into passes as +//! [`crate::quantum_sv`] groups them. Each threadgroup copies a tile of +//! `2^`[`TILE_BITS`] amplitudes into threadgroup memory, applies every gate +//! of the pass there between barriers, and copies it back. Every amplitude +//! goes through the same operations as gate by gate, so tiles change no bit +//! (a test holds tiled and untiled runs equal). Each group's members are held +//! in registers, and a kernel is built per group size, so a pass of small +//! gates does not pay the registers of large ones. +//! - **Readouts.** Norms, Pauli expectation values and samples are read from +//! the shared buffer on the host and summed in `f64`, in fixed chunks. +//! +//! # Checked +//! +//! - **Against the double-precision state vector** on random circuits of 6 to +//! 21 qubits, at every fusion width: every amplitude within `2·10⁻⁶`, norms +//! within `10⁻⁵`, expectation values within `10⁻⁵`. +//! - **Speed** on a desktop machine's GPU, under heavy load from other +//! work: a 30-qubit, depth-20 brickwork of 910 gates runs in about 4.2 s at +//! fusion width 3 with tiles, against about 8 s gate by gate. Fusion width 4 +//! is slower than 3 on this GPU. +//! +//! # Honest boundaries +//! +//! - **Single precision.** Amplitudes carry `f32` rounding, about `10⁻⁷` +//! relative per gate, growing with the square root of the gate count for +//! unitary circuits. The double-precision state vector is the reference; +//! this one is checked against it. +//! - **Not bit-identical across devices.** The result is reproducible on one +//! device and operating system; another GPU or compiler may round +//! differently. +//! - **macOS only.** Elsewhere [`GpuState::zero`] reports that no device is +//! available. + +use crate::gates::{C, Gate}; + +/// The widest fused gate the kernels take (`2⁴` amplitudes a group). +pub const MAX_WIDTH: usize = 4; + +/// Why the GPU state vector could not run. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum GpuError { + /// No Metal device, or not on macOS. + Unavailable(String), + /// More qubits than the device's largest buffer holds. + TooLarge { qubits: u32, max: u32 }, + /// A gate on no qubits, a repeated qubit, a qubit outside the register, + /// or a matrix of the wrong size. + BadGate(usize), + /// The device refused the kernel or a command. + Device(String), +} + +impl std::fmt::Display for GpuError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + GpuError::Unavailable(why) => write!(f, "no GPU state vector here: {why}"), + GpuError::TooLarge { qubits, max } => write!(f, "{qubits} qubits; this device's buffers hold at most {max}"), + GpuError::BadGate(i) => write!(f, "gate {i} is not a gate on the register"), + GpuError::Device(why) => write!(f, "the device: {why}"), + } + } +} + +impl std::error::Error for GpuError {} + +/// The kernels, in the platform's shading language. `apply_dense` takes a +/// thread per group of `2^k` amplitudes; `apply_diag` four amplitudes a +/// thread; `clear` zeroes the state, four amplitudes a thread. +pub const SOURCE: &str = r#" +#include +using namespace metal; + +// One group's update: row r of the matrix against the group's members, summed +// in member order. D is a compile-time constant, so the group stays in +// registers. +template +inline void group_fixed(P psi, ulong b, constant uint* offsets, constant float2* m) { + float2 v[D]; + for (uint j = 0; j < D; j++) { + v[j] = psi[b + offsets[j]]; + } + for (uint r = 0; r < D; r++) { + float sr = 0.0f; + float si = 0.0f; + for (uint c = 0; c < D; c++) { + float2 e = m[r * D + c]; + sr += e.x * v[c].x - e.y * v[c].y; + si += e.x * v[c].y + e.y * v[c].x; + } + psi[b + offsets[r]] = float2(sr, si); + } +} + +// Groups of up to MAXD members: each kernel below is built once per MAXD, so +// a pass of small gates does not pay the registers of large ones. +template +inline void group_update(P psi, ulong b, uint d, constant uint* offsets, constant float2* m) { + if (d == 2) { group_fixed<2>(psi, b, offsets, m); return; } + if (MAXD >= 4 && d == 4) { group_fixed<4>(psi, b, offsets, m); return; } + if (MAXD >= 8 && d == 8) { group_fixed<8>(psi, b, offsets, m); return; } + if (MAXD >= 16) { group_fixed<16>(psi, b, offsets, m); } +} + +struct Dense { + uint k; + uint d; + uint sorted[4]; + uint offsets[16]; +}; + +template +kernel void apply_dense(device float2* psi [[buffer(0)]], + constant Dense& g [[buffer(1)]], + constant float2* m [[buffer(2)]], + uint t [[thread_position_in_grid]]) { + ulong base = t; + for (uint i = 0; i < g.k; i++) { + uint p = g.sorted[i]; + ulong low = base & ((1ul << p) - 1ul); + base = ((base >> p) << (p + 1)) | low; + } + group_update(psi, base, g.d, g.offsets, m); +} + +template [[host_name("apply_dense_2")]] kernel void apply_dense<2>(device float2*, constant Dense&, constant float2*, uint); +template [[host_name("apply_dense_4")]] kernel void apply_dense<4>(device float2*, constant Dense&, constant float2*, uint); +template [[host_name("apply_dense_8")]] kernel void apply_dense<8>(device float2*, constant Dense&, constant float2*, uint); +template [[host_name("apply_dense_16")]] kernel void apply_dense<16>(device float2*, constant Dense&, constant float2*, uint); + +struct Diag { + uint k; + uint bitpos[4]; +}; + +kernel void apply_diag(device float2* psi [[buffer(0)]], + constant Diag& g [[buffer(1)]], + constant float2* diag [[buffer(2)]], + uint t [[thread_position_in_grid]]) { + for (uint a = 0; a < 4; a++) { + ulong x = (ulong)t * 4ul + a; + uint local = 0; + for (uint i = 0; i < g.k; i++) { + local = (local << 1) | (uint)((x >> g.bitpos[i]) & 1ul); + } + float2 c = diag[local]; + if (c.x == 1.0f && c.y == 0.0f) { + continue; + } + float2 v = psi[x]; + psi[x] = float2(c.x * v.x - c.y * v.y, c.x * v.y + c.y * v.x); + } +} + +struct TileGate { + uint kind; + uint k; + uint d; + uint mat; + uint sorted[4]; + uint offsets[16]; + uint place[4]; + uint local_mask; + uint pad[3]; +}; + +struct Tile { + uint run_bits; + uint free_count; + uint outer_count; + uint gates; + uint free_bits[16]; + uint outer_bits[48]; +}; + +template +kernel void apply_tile(device float2* psi [[buffer(0)]], + constant Tile& a [[buffer(1)]], + constant TileGate* gates [[buffer(2)]], + constant float2* mats [[buffer(3)]], + threadgroup float2* tile [[threadgroup(0)]], + uint tg [[threadgroup_position_in_grid]], + uint lid [[thread_index_in_threadgroup]], + uint nt [[threads_per_threadgroup]]) { + ulong base = 0; + for (uint i = 0; i < a.outer_count; i++) { + base |= (ulong)((tg >> i) & 1u) << a.outer_bits[i]; + } + uint len = 1u << (a.run_bits + a.free_count); + uint run_mask = (1u << a.run_bits) - 1u; + for (uint s = lid; s < len; s += nt) { + ulong x = base | (ulong)(s & run_mask); + for (uint i = 0; i < a.free_count; i++) { + x |= (ulong)((s >> (a.run_bits + i)) & 1u) << a.free_bits[i]; + } + tile[s] = psi[x]; + } + threadgroup_barrier(mem_flags::mem_threadgroup); + for (uint gi = 0; gi < a.gates; gi++) { + constant TileGate& g = gates[gi]; + if (g.kind == 0 && g.d == 16) { + // Sixteen members: each thread forms single outputs, its row of + // the matrix against the group in threadgroup memory, holds them, + // and writes them after every thread has read. + float2 outv[8]; + uint n_out = 0; + for (uint s = lid; s < len; s += nt) { + uint r = 0; + uint b = s; + for (uint i = 0; i < 4; i++) { + r |= ((s >> g.place[i]) & 1u) << (3u - i); + b &= ~(1u << g.place[i]); + } + float sr = 0.0f; + float si = 0.0f; + for (uint c = 0; c < 16; c++) { + float2 e = mats[g.mat + r * 16 + c]; + float2 v = tile[b + g.offsets[c]]; + sr += e.x * v.x - e.y * v.y; + si += e.x * v.y + e.y * v.x; + } + outv[n_out++] = float2(sr, si); + } + threadgroup_barrier(mem_flags::mem_threadgroup); + n_out = 0; + for (uint s = lid; s < len; s += nt) { + tile[s] = outv[n_out++]; + } + } else if (g.kind == 0) { + uint groups = len >> g.k; + for (uint t = lid; t < groups; t += nt) { + uint b = t; + for (uint i = 0; i < g.k; i++) { + uint p = g.sorted[i]; + uint low = b & ((1u << p) - 1u); + b = ((b >> p) << (p + 1)) | low; + } + group_update(tile, (ulong)b, g.d, g.offsets, mats + g.mat); + } + } else { + uint fixed = 0; + for (uint i = 0; i < g.k; i++) { + if (((g.local_mask >> i) & 1u) == 0u) { + fixed |= (uint)((base >> g.place[i]) & 1ul) << (g.k - 1u - i); + } + } + for (uint s = lid; s < len; s += nt) { + uint local = fixed; + for (uint i = 0; i < g.k; i++) { + if (((g.local_mask >> i) & 1u) != 0u) { + local |= ((s >> g.place[i]) & 1u) << (g.k - 1u - i); + } + } + float2 c = mats[g.mat + local]; + if (c.x == 1.0f && c.y == 0.0f) { + continue; + } + float2 v = tile[s]; + tile[s] = float2(c.x * v.x - c.y * v.y, c.x * v.y + c.y * v.x); + } + } + threadgroup_barrier(mem_flags::mem_threadgroup); + } + for (uint s = lid; s < len; s += nt) { + ulong x = base | (ulong)(s & run_mask); + for (uint i = 0; i < a.free_count; i++) { + x |= (ulong)((s >> (a.run_bits + i)) & 1u) << a.free_bits[i]; + } + psi[x] = tile[s]; + } +} + +template [[host_name("apply_tile_2")]] kernel void apply_tile<2>(device float2*, constant Tile&, constant TileGate*, constant float2*, threadgroup float2*, uint, uint, uint); +template [[host_name("apply_tile_4")]] kernel void apply_tile<4>(device float2*, constant Tile&, constant TileGate*, constant float2*, threadgroup float2*, uint, uint, uint); +template [[host_name("apply_tile_8")]] kernel void apply_tile<8>(device float2*, constant Tile&, constant TileGate*, constant float2*, threadgroup float2*, uint, uint, uint); +template [[host_name("apply_tile_16")]] kernel void apply_tile<16>(device float2*, constant Tile&, constant TileGate*, constant float2*, threadgroup float2*, uint, uint, uint); + +kernel void clear(device float4* psi [[buffer(0)]], uint t [[thread_position_in_grid]]) { + psi[2ul * t] = float4(0.0f); + psi[2ul * t + 1ul] = float4(0.0f); +} +"#; + +/// The layout `apply_dense` reads at buffer index 1. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct DenseArgs { + k: u32, + d: u32, + sorted: [u32; 4], + offsets: [u32; 16], +} + +/// The layout `apply_diag` reads at buffer index 1. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct DiagArgs { + k: u32, + bitpos: [u32; 4], +} + +/// The layout `apply_tile` reads for each gate. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct TileGate { + kind: u32, + k: u32, + d: u32, + mat: u32, + sorted: [u32; 4], + offsets: [u32; 16], + place: [u32; 4], + local_mask: u32, + pad: [u32; 3], +} + +/// The layout `apply_tile` reads at buffer index 1. +#[repr(C)] +#[derive(Clone, Copy, Debug)] +struct TileArgs { + run_bits: u32, + free_count: u32, + outer_count: u32, + gates: u32, + free_bits: [u32; 16], + outer_bits: [u32; 48], +} + +/// Tile bits on the GPU: 4096 amplitudes, 32 KiB of threadgroup memory. +pub const TILE_BITS: u32 = 12; +/// The bottom bits every tile holds, so each copy moves 32 neighbours. +const TILE_RUN_BITS: u32 = 5; + +/// One fused gate, or one tile pass of several, ready to encode. +#[cfg_attr(not(target_os = "macos"), allow(dead_code))] +enum Pass { + Dense(DenseArgs, Vec<[f32; 2]>), + Diag(DiagArgs, Vec<[f32; 2]>), + Tile(TileArgs, Vec, Vec<[f32; 2]>), +} + +/// The passes of a run: tile passes as [`crate::quantum_sv::plan`] groups the +/// gates (unless `tiled` is false), single gates otherwise. +fn plan_passes(n: u32, gates: &[Gate], tiled: bool) -> Vec { + if !tiled || n < TILE_BITS + 2 { + return passes(n, gates); + } + let mut out = Vec::new(); + for stage in crate::quantum_sv::plan(gates, n, TILE_BITS, TILE_RUN_BITS) { + match stage { + crate::quantum_sv::Stage::Direct(i) => out.extend(passes(n, &gates[i..=i])), + crate::quantum_sv::Stage::Tiled { gates: ids, local } => { + // One gate on at most three qubits needs no tile. + if ids.len() == 1 && (gates[ids[0]].qubits.len() <= 3 || gates[ids[0]].is_diagonal()) { + out.extend(passes(n, &gates[ids[0]..=ids[0]])); + continue; + } + out.push(tile_pass(n, &ids.iter().map(|&i| &gates[i]).collect::>(), &local)); + } + } + } + out +} + +fn tile_pass(n: u32, gates: &[&Gate], local: &[u32]) -> Pass { + // The local bits above the run, ascending; those extending the run join it. + let mut run = TILE_RUN_BITS; + let mut free: Vec = local.to_vec(); + free.sort_unstable(); + while let Some(i) = free.iter().position(|&p| p == run) { + free.remove(i); + run += 1; + } + let outer: Vec = (run..n).filter(|p| !free.contains(p)).collect(); + let in_tile = |p: u32| -> Option { + if p < run { + Some(p) + } else { + free.iter().position(|&f| f == p).map(|i| run + i as u32) + } + }; + let mut args = TileArgs { run_bits: run, free_count: free.len() as u32, outer_count: outer.len() as u32, gates: gates.len() as u32, free_bits: [0; 16], outer_bits: [0; 48] }; + args.free_bits[..free.len()].copy_from_slice(&free); + args.outer_bits[..outer.len()].copy_from_slice(&outer); + let mut descs = Vec::with_capacity(gates.len()); + let mut mats: Vec<[f32; 2]> = Vec::new(); + for g in gates { + let k = g.qubits.len(); + let d = 1usize << k; + let bitpos: Vec = g.qubits.iter().map(|&q| n - 1 - q).collect(); + let mut t = TileGate { k: k as u32, d: d as u32, mat: mats.len() as u32, ..Default::default() }; + if g.is_diagonal() { + t.kind = 1; + for (i, &p) in bitpos.iter().enumerate() { + match in_tile(p) { + Some(sp) => { + t.place[i] = sp; + t.local_mask |= 1 << i; + } + None => t.place[i] = p, + } + } + mats.extend((0..d).map(|i| [g.matrix[i * d + i].re as f32, g.matrix[i * d + i].im as f32])); + } else { + let pos: Vec = bitpos.iter().map(|&p| in_tile(p).expect("a dense gate on local bits")).collect(); + let mut sorted = pos.clone(); + sorted.sort_unstable(); + t.sorted[..k].copy_from_slice(&sorted); + t.place[..k].copy_from_slice(&pos); + for l in 0..d { + t.offsets[l] = pos.iter().enumerate().fold(0u32, |o, (i, &p)| o | ((((l >> (k - 1 - i)) & 1) as u32) << p)); + } + mats.extend(g.matrix.iter().map(|c| [c.re as f32, c.im as f32])); + } + descs.push(t); + } + Pass::Tile(args, descs, mats) +} + +fn passes(n: u32, gates: &[Gate]) -> Vec { + gates + .iter() + .map(|g| { + let k = g.qubits.len(); + let d = 1usize << k; + let bitpos: Vec = g.qubits.iter().map(|&q| n - 1 - q).collect(); + let f = |c: &C| [c.re as f32, c.im as f32]; + if g.is_diagonal() { + let mut a = DiagArgs { k: k as u32, ..Default::default() }; + a.bitpos[..k].copy_from_slice(&bitpos); + Pass::Diag(a, (0..d).map(|i| f(&g.matrix[i * d + i])).collect()) + } else { + let mut sorted = bitpos.clone(); + sorted.sort_unstable(); + let mut a = DenseArgs { k: k as u32, d: d as u32, ..Default::default() }; + a.sorted[..k].copy_from_slice(&sorted); + for l in 0..d { + a.offsets[l] = bitpos.iter().enumerate().fold(0u32, |o, (i, &p)| o | ((((l >> (k - 1 - i)) & 1) as u32) << p)); + } + Pass::Dense(a, g.matrix.iter().map(f).collect()) + } + }) + .collect() +} + +/// A pure state of `n` qubits on the GPU. +pub struct GpuState { + pub n: u32, + inner: sys::State, +} + +impl std::fmt::Debug for GpuState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "GpuState({} qubits on {})", self.n, self.device()) + } +} + +impl GpuState { + /// `|0…0⟩` on `n` qubits, on the platform's default GPU. + pub fn zero(n: u32) -> Result { + Ok(GpuState { n, inner: sys::State::zero(n)? }) + } + + /// The most qubits the default GPU's largest buffer holds, if there is one. + pub fn max_qubits() -> Result { + sys::max_qubits() + } + + /// The device's name. + pub fn device(&self) -> String { + self.inner.device() + } + + /// Apply `gates` in order, fused to `width` qubits first (at most + /// [`MAX_WIDTH`]; 0 or 1 applies them as given), in one command buffer. + /// Consecutive gates go in tile passes: each threadgroup copies a tile of + /// `2^`[`TILE_BITS`] amplitudes into threadgroup memory, applies every gate + /// of the pass there, and copies it back. Every amplitude goes through the + /// same operations as gate by gate, so tiling changes no bit. + pub fn run(&mut self, gates: &[Gate], width: usize) -> Result<(), GpuError> { + self.run_tiled(gates, width, true) + } + + /// [`run`](Self::run), with tiling on or off. + pub fn run_tiled(&mut self, gates: &[Gate], width: usize, tiled: bool) -> Result<(), GpuError> { + for (i, g) in gates.iter().enumerate() { + let k = g.qubits.len(); + let mut q = g.qubits.clone(); + q.sort_unstable(); + q.dedup(); + if k == 0 || k > MAX_WIDTH || q.len() != k || g.qubits.iter().any(|&x| x >= self.n) || g.matrix.len() != 1 << (2 * k) { + return Err(GpuError::BadGate(i)); + } + } + let width = width.min(MAX_WIDTH); + let fused; + let list = if width >= 2 { + fused = crate::quantum_sv::fuse(gates, width); + &fused + } else { + gates + }; + self.inner.run(&plan_passes(self.n, list, tiled)) + } + + /// The amplitudes as `(re, im)` pairs, borrowed from the shared buffer. + pub fn amplitudes(&self) -> &[[f32; 2]] { + self.inner.amplitudes() + } + + /// The amplitude of basis state `x`, widened. + pub fn amplitude(&self, x: usize) -> C { + let a = self.amplitudes()[x]; + C { re: f64::from(a[0]), im: f64::from(a[1]) } + } + + /// `Σ |amplitude|²`, in `f64`. + pub fn norm2(&self) -> f64 { + chunked_sum(self.amplitudes(), &|_, a| { + let (r, i) = (f64::from(a[0]), f64::from(a[1])); + r * r + i * i + }) + } + + /// `⟨ψ| Σ c·P |ψ⟩` for Pauli strings `(qubit, 'X'|'Y'|'Z')`, in `f64`. + pub fn expectation(&self, observable: &[(f64, Vec<(u32, char)>)]) -> f64 { + let (n, amps) = (self.n, self.amplitudes()); + let mut total = 0.0; + for (coef, string) in observable { + let (mut flip, mut zmask, mut ys) = (0usize, 0usize, 0u32); + for &(q, p) in string { + let bit = 1usize << (n - 1 - q); + match p { + 'X' => flip |= bit, + 'Y' => { + flip |= bit; + zmask |= bit; + ys += 1; + } + 'Z' => zmask |= bit, + _ => {} + } + } + let v = chunked_sum(amps, &|x, b| { + let a = amps[x ^ flip]; + let sign = if (x & zmask).count_ones().is_multiple_of(2) { 1.0 } else { -1.0 }; + let (ar, ai) = (f64::from(a[0]), -f64::from(a[1])); + let (br, bi) = (f64::from(b[0]), f64::from(b[1])); + let (pr, pi) = (ar * br - ai * bi, ar * bi + ai * br); + sign * match ys % 4 { + 0 => pr, + 1 => -pi, + 2 => -pr, + _ => pi, + } + }); + total += coef * v; + } + total + } + + /// `shots` computational-basis outcomes from a seeded stream, as + /// [`crate::quantum_sv::StateVector::sample`] draws them. + pub fn sample(&self, shots: usize, seed: u64) -> Vec { + let amps = self.amplitudes(); + let mut s = seed; + let mut u: Vec<(f64, usize)> = (0..shots) + .map(|i| { + s = s.wrapping_add(0x9e37_79b9_7f4a_7c15); + let mut z = s; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); + ((((z ^ (z >> 31)) >> 11) as f64 + 0.5) / 9_007_199_254_740_992.0, i) + }) + .collect(); + u.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap().then(a.1.cmp(&b.1))); + let total = self.norm2(); + let mut out = vec![0u64; shots]; + let (mut cum, mut k) = (0.0, 0); + let last = amps.len() - 1; + for (x, a) in amps.iter().enumerate() { + let (r, i) = (f64::from(a[0]), f64::from(a[1])); + cum += (r * r + i * i) / total; + while k < shots && (u[k].0 < cum || x == last) { + out[u[k].1] = x as u64; + k += 1; + } + if k == shots { + break; + } + } + out + } +} + +/// `Σ_x term(x, amps[x])` in 64 fixed chunks, each summed in order and the +/// chunks added in order, the chunks shared between threads. +fn chunked_sum(amps: &[[f32; 2]], term: &(dyn Fn(usize, &[f32; 2]) -> f64 + Sync)) -> f64 { + let len = amps.len(); + let chunks = len.min(64); + let per = len / chunks; + let sum = |c: usize| -> f64 { + let mut s = 0.0; + for x in c * per..(c + 1) * per { + s += term(x, &s[x]); + } + s + }; + let threads = std::thread::available_parallelism().map_or(1, |t| t.get()).min(chunks); + let parts: Vec = if threads > 1 && len >= 1 << 16 { + let size = chunks.div_ceil(threads); + std::thread::scope(|sc| { + let hs: Vec<_> = (0..chunks) + .step_by(size) + .map(|lo| { + let sum = ∑ + sc.spawn(move || (lo..(lo + size).min(chunks)).map(sum).collect::>()) + }) + .collect(); + hs.into_iter().flat_map(|h| h.join().expect("a chunk")).collect() + }) + } else { + (0..chunks).map(sum).collect() + }; + parts.into_iter().fold(0.0, |a, b| a + b) +} + +#[cfg(target_os = "macos")] +mod sys { + use super::{GpuError, Pass, SOURCE}; + use objc2::rc::{Retained, autoreleasepool}; + use objc2::runtime::ProtocolObject; + use objc2_foundation::{NSError, NSString}; + use objc2_metal::{ + MTLBuffer, MTLCommandBuffer, MTLCommandBufferStatus, MTLCommandEncoder, MTLCommandQueue, MTLCompileOptions, MTLComputeCommandEncoder, MTLComputePipelineState, + MTLCreateSystemDefaultDevice, MTLDevice, MTLLibrary, MTLMathMode, MTLResourceOptions, MTLSize, + }; + use std::ptr::NonNull; + + type Pipeline = Retained>; + + pub struct State { + n: u32, + device: Retained>, + queue: Retained>, + /// By group size 2, 4, 8, 16. + dense: [Pipeline; 4], + diag: Pipeline, + tile: [Pipeline; 4], + buf: Retained>, + } + + /// The pipeline slot for groups of up to `d` members. + fn slot(d: u32) -> usize { + match d { + 0..=2 => 0, + 3..=4 => 1, + 5..=8 => 2, + _ => 3, + } + } + + fn ns(e: &NSError) -> String { + e.localizedDescription().to_string() + } + + fn qubits_in(bytes: u64) -> u32 { + // 8 bytes an amplitude. + 63 - (bytes / 8).max(1).leading_zeros() + } + + pub fn max_qubits() -> Result { + let device = MTLCreateSystemDefaultDevice().ok_or_else(|| GpuError::Unavailable("the platform has no default Metal device".into()))?; + Ok(qubits_in(device.maxBufferLength() as u64)) + } + + #[cfg(test)] + pub fn tile_limits() -> Vec { + State::zero(4).map(|s| s.limits()).unwrap_or_default() + } + + fn size(w: u64) -> MTLSize { + MTLSize { width: w as usize, height: 1, depth: 1 } + } + + impl State { + pub fn zero(n: u32) -> Result { + autoreleasepool(|_| { + let device = MTLCreateSystemDefaultDevice().ok_or_else(|| GpuError::Unavailable("the platform has no default Metal device".into()))?; + let max = qubits_in(device.maxBufferLength() as u64); + if n > max || n < 3 { + return Err(GpuError::TooLarge { qubits: n, max }); + } + let queue = device.newCommandQueue().ok_or_else(|| GpuError::Device("no command queue".into()))?; + let options = MTLCompileOptions::new(); + options.setMathMode(MTLMathMode::Safe); + let library = device.newLibraryWithSource_options_error(&NSString::from_str(SOURCE), Some(&options)).map_err(|e| GpuError::Device(ns(&e)))?; + let pipeline = |name: &str| -> Result { + let f = library.newFunctionWithName(&NSString::from_str(name)).ok_or_else(|| GpuError::Device(format!("no entry point {name}")))?; + device.newComputePipelineStateWithFunction_error(&f).map_err(|e| GpuError::Device(ns(&e))) + }; + let dense = [pipeline("apply_dense_2")?, pipeline("apply_dense_4")?, pipeline("apply_dense_8")?, pipeline("apply_dense_16")?]; + let tile = [pipeline("apply_tile_2")?, pipeline("apply_tile_4")?, pipeline("apply_tile_8")?, pipeline("apply_tile_16")?]; + let (diag, clear) = (pipeline("apply_diag")?, pipeline("clear")?); + let bytes = 8usize << n; + let buf = device.newBufferWithLength_options(bytes, MTLResourceOptions::StorageModeShared).ok_or_else(|| GpuError::Device(format!("a buffer of {bytes} bytes")))?; + let s = State { n, device, queue, dense, diag, tile, buf }; + // Zero the state on the device, then set |0…0⟩ from the host. + s.submit(|enc| { + enc.setComputePipelineState(&clear); + // SAFETY: the buffer is bound at the index the kernel reads. + unsafe { enc.setBuffer_offset_atIndex(Some(&s.buf), 0, 0) }; + let threads = (1u64 << n) / 4; + let tg = (clear.maxTotalThreadsPerThreadgroup() as u64).min(256).min(threads); + enc.dispatchThreadgroups_threadsPerThreadgroup(size(threads / tg), size(tg)); + })?; + // SAFETY: the buffer holds 2ⁿ pairs of f32, and the command + // buffer that cleared it has completed. + unsafe { *s.buf.contents().as_ptr().cast::<[f32; 2]>() = [1.0, 0.0] }; + Ok(s) + }) + } + + pub fn device(&self) -> String { + self.device.name().to_string() + } + + #[cfg(test)] + pub fn limits(&self) -> Vec { + self.tile.iter().map(|p| p.maxTotalThreadsPerThreadgroup()).collect() + } + + fn submit(&self, encode: impl FnOnce(&ProtocolObject)) -> Result<(), GpuError> { + let cb = self.queue.commandBuffer().ok_or_else(|| GpuError::Device("no command buffer".into()))?; + let enc = cb.computeCommandEncoder().ok_or_else(|| GpuError::Device("no compute encoder".into()))?; + encode(&enc); + enc.endEncoding(); + cb.commit(); + cb.waitUntilCompleted(); + if cb.status() != MTLCommandBufferStatus::Completed { + let why = cb.error().map(|e| ns(&e)).unwrap_or_else(|| format!("status {:?}", cb.status())); + return Err(GpuError::Device(format!("the command buffer did not complete: {why}"))); + } + Ok(()) + } + + pub fn run(&mut self, passes: &[Pass]) -> Result<(), GpuError> { + let n = self.n; + autoreleasepool(|_| { + // Each pass's matrix in a buffer of its own, so all can be + // encoded at once. + let mut mats = Vec::with_capacity(passes.len()); + for p in passes { + let m = match p { + Pass::Dense(_, m) | Pass::Diag(_, m) | Pass::Tile(_, _, m) => m, + }; + let bytes = std::mem::size_of_val(m.as_slice()); + let b = self.device.newBufferWithLength_options(bytes.max(8), MTLResourceOptions::StorageModeShared).ok_or_else(|| GpuError::Device("a matrix buffer".into()))?; + // SAFETY: the buffer holds `bytes` bytes, and no command + // using it has been encoded. + unsafe { std::ptr::copy_nonoverlapping(m.as_ptr().cast::(), b.contents().as_ptr().cast::(), bytes) }; + mats.push(b); + } + // Each tile pass's gate list in a buffer of its own. + let mut descs = Vec::with_capacity(passes.len()); + for p in passes { + if let Pass::Tile(_, d, _) = p { + let bytes = std::mem::size_of_val(d.as_slice()); + let b = self.device.newBufferWithLength_options(bytes.max(8), MTLResourceOptions::StorageModeShared).ok_or_else(|| GpuError::Device("a gate-list buffer".into()))?; + // SAFETY: as for the matrices. + unsafe { std::ptr::copy_nonoverlapping(d.as_ptr().cast::(), b.contents().as_ptr().cast::(), bytes) }; + descs.push(Some(b)); + } else { + descs.push(None); + } + } + self.submit(|enc| { + for ((p, m), d) in passes.iter().zip(&mats).zip(&descs) { + if let (Pass::Tile(a, gates, _), Some(d)) = (p, d) { + // SAFETY: the state at 0, the gate list at 2 and the + // matrices at 3, each sized for what the kernel reads; + // the arguments' layout is `Tile`'s. + unsafe { + enc.setBuffer_offset_atIndex(Some(&self.buf), 0, 0); + enc.setBytes_length_atIndex(NonNull::from(a).cast(), std::mem::size_of_val(a), 1); + enc.setBuffer_offset_atIndex(Some(d), 0, 2); + enc.setBuffer_offset_atIndex(Some(m), 0, 3); + } + let len = 1u64 << (a.run_bits + a.free_count); + let widest = gates.iter().filter(|g| g.kind == 0).map(|g| g.d).max().unwrap_or(2); + let pipe = &self.tile[slot(widest)]; + enc.setComputePipelineState(pipe); + // SAFETY: the tile is `len` complex values of 8 + // bytes, within the device's 32 KiB. + unsafe { enc.setThreadgroupMemoryLength_atIndex((len * 8) as usize, 0) }; + // A sixteen-member gate holds at most eight outputs a thread. + let tg = (pipe.maxTotalThreadsPerThreadgroup() as u64).min(512).max(len / 8); + enc.dispatchThreadgroups_threadsPerThreadgroup(size(1u64 << a.outer_count), size(tg)); + continue; + } + // SAFETY: each buffer is bound at the index its kernel + // parameter names, and is sized for what it reads. + unsafe { + enc.setBuffer_offset_atIndex(Some(&self.buf), 0, 0); + enc.setBuffer_offset_atIndex(Some(m), 0, 2); + } + let (pipe, threads) = match p { + Pass::Dense(a, _) => { + // SAFETY: the kernel reads exactly a `Dense`, whose layout `DenseArgs` repeats. + unsafe { enc.setBytes_length_atIndex(NonNull::from(a).cast(), std::mem::size_of_val(a), 1) }; + (&self.dense[slot(a.d)], (1u64 << n) >> a.k) + } + Pass::Diag(a, _) => { + // SAFETY: as above, for `Diag`. + unsafe { enc.setBytes_length_atIndex(NonNull::from(a).cast(), std::mem::size_of_val(a), 1) }; + (&self.diag, (1u64 << n) / 4) + } + Pass::Tile(..) => unreachable!("encoded above"), + }; + enc.setComputePipelineState(pipe); + let tg = (pipe.maxTotalThreadsPerThreadgroup() as u64).min(256).min(threads); + enc.dispatchThreadgroups_threadsPerThreadgroup(size(threads / tg), size(tg)); + } + }) + }) + } + + pub fn amplitudes(&self) -> &[[f32; 2]] { + // SAFETY: the buffer holds 2ⁿ pairs of f32 and lives as long as + // `self`; every command buffer writing it has completed before any + // method returns, and `run` takes `&mut self`. + unsafe { std::slice::from_raw_parts(self.buf.contents().as_ptr().cast::<[f32; 2]>(), 1usize << self.n) } + } + } +} + +#[cfg(not(target_os = "macos"))] +mod sys { + use super::{GpuError, Pass}; + + pub struct State; + + pub fn max_qubits() -> Result { + Err(GpuError::Unavailable("the GPU state vector runs on macOS".into())) + } + + impl State { + pub fn zero(_: u32) -> Result { + Err(GpuError::Unavailable("the GPU state vector runs on macOS".into())) + } + pub fn device(&self) -> String { + String::new() + } + pub fn run(&mut self, _: &[Pass]) -> Result<(), GpuError> { + Err(GpuError::Unavailable("the GPU state vector runs on macOS".into())) + } + pub fn amplitudes(&self) -> &[[f32; 2]] { + &[] + } + } +} + +#[cfg(all(test, target_os = "macos"))] +mod tests { + use super::*; + use crate::quantum_sv::StateVector; + + fn rng(seed: u64) -> impl FnMut() -> f64 { + let mut s = seed; + move || { + s = s.wrapping_add(0x9e37_79b9_7f4a_7c15); + let mut z = s; + z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb); + (((z ^ (z >> 31)) >> 11) as f64 + 0.5) / 9_007_199_254_740_992.0 + } + } + + /// Without a device the tests have nothing to check; where one is + /// declared (`WAI_QUANTUM_SV_METAL_EXPECT`), its absence is a failure. + fn no_device() { + assert!(std::env::var_os("WAI_QUANTUM_SV_METAL_EXPECT").is_none(), "a Metal device was declared but none was found"); + eprintln!("no Metal device: skipped"); + } + + fn circuit(n: u32, depth: usize, seed: u64) -> Vec { + let mut u = rng(seed); + let mut g = Vec::new(); + for layer in 0..depth { + for q in 0..n { + g.push(match (u() * 4.0) as u32 { + 0 => Gate::rx(q, u() * 6.0), + 1 => Gate::ry(q, u() * 6.0), + 2 => Gate::rz(q, u() * 6.0), + _ => Gate::h(q), + }); + } + let mut q = (layer % 2) as u32; + while q + 1 < n { + g.push(if layer % 3 == 0 { Gate::fsim(q, q + 1, u(), u()) } else { Gate::cphase(q, q + 1, u() * 3.0) }); + q += 2; + } + let (a, b) = ((u() * n as f64) as u32, (u() * n as f64) as u32); + if a != b { + g.push(Gate::iswap(a, b)); + } + } + g + } + + #[test] + fn it_follows_the_double_precision_state_vector() { + let Ok(_) = GpuState::max_qubits() else { + no_device(); + return; + }; + for (n, depth, seed) in [(6u32, 6usize, 1u64), (12, 8, 2), (18, 10, 3), (21, 8, 4)] { + let gates = circuit(n, depth, seed); + for width in [0usize, 2, 3, 4] { + let mut want = StateVector::zero(n).unwrap(); + want.run(&gates, width).unwrap(); + let mut got = GpuState::zero(n).unwrap(); + got.run(&gates, width).unwrap(); + let worst = (0..1usize << n).map(|x| { + let a = got.amplitude(x); + (a.re - want.re[x]).abs().max((a.im - want.im[x]).abs()) + }).fold(0.0, f64::max); + assert!(worst < 2e-6, "n {n} width {width}: {worst}"); + assert!((got.norm2() - 1.0).abs() < 1e-5); + let obs = vec![(0.7, vec![(0, 'Z'), (n - 1, 'X')]), (0.3, vec![(1, 'Y')])]; + assert!((got.expectation(&obs) - want.expectation(&obs)).abs() < 1e-5, "n {n} width {width}"); + } + } + } + + #[test] + fn runs_repeat_bit_for_bit_and_samples_follow_the_state() { + let Ok(_) = GpuState::max_qubits() else { + no_device(); + return; + }; + let n = 16; + let gates = circuit(n, 6, 9); + let mut a = GpuState::zero(n).unwrap(); + a.run(&gates, 3).unwrap(); + let mut b = GpuState::zero(n).unwrap(); + b.run(&gates, 3).unwrap(); + assert!(a.amplitudes().iter().zip(b.amplitudes()).all(|(x, y)| x[0].to_bits() == y[0].to_bits() && x[1].to_bits() == y[1].to_bits())); + let s = a.sample(20_000, 3); + assert_eq!(s, a.sample(20_000, 3)); + // The most likely outcome's frequency against its probability. + let (x, p) = (0..1usize << n).map(|x| (x, a.amplitude(x).re.powi(2) + a.amplitude(x).im.powi(2))).fold((0, 0.0), |m, v| if v.1 > m.1 { v } else { m }); + let f = s.iter().filter(|&&y| y == x as u64).count() as f64 / 20_000.0; + assert!((f - p).abs() < 5.0 * (p / 20_000.0).sqrt() + 1e-3, "{f} vs {p}"); + } + + #[test] + fn tiles_change_no_bit() { + let Ok(_) = GpuState::max_qubits() else { + no_device(); + return; + }; + for (n, seed) in [(15u32, 4u64), (19, 5)] { + let mut gates = circuit(n, 8, seed); + // Diagonal gates on far-apart qubits, which a tile holds fixed. + gates.push(Gate::cphase(0, n - 1, 0.7)); + gates.push(Gate::rzz(2, n - 3, 0.4)); + for width in [0usize, 2, 3, 4] { + let mut a = GpuState::zero(n).unwrap(); + a.run_tiled(&gates, width, false).unwrap(); + let mut b = GpuState::zero(n).unwrap(); + b.run_tiled(&gates, width, true).unwrap(); + let same = a.amplitudes().iter().zip(b.amplitudes()).all(|(x, y)| x[0].to_bits() == y[0].to_bits() && x[1].to_bits() == y[1].to_bits()); + assert!(same, "n {n} width {width}"); + } + } + } + + #[test] + fn bad_gates_are_refused() { + let Ok(_) = GpuState::max_qubits() else { + no_device(); + return; + }; + let mut s = GpuState::zero(4).unwrap(); + assert_eq!(s.run(&[Gate::cx(0, 4)], 0).unwrap_err(), GpuError::BadGate(0)); + assert_eq!(s.run(&[Gate::h(0), Gate { qubits: vec![1, 1], matrix: vec![C { re: 1.0, im: 0.0 }; 16] }], 0).unwrap_err(), GpuError::BadGate(1)); + } + + /// Times at scale; run with `--ignored --nocapture`. + #[test] + #[ignore] + fn timings_at_scale() { + println!("device: {}", GpuState::zero(4).unwrap().device()); + println!("threads a threadgroup, by group size: {:?}", sys::tile_limits()); + for n in [24u32, 28, 30] { + let gates = circuit(n, 20, 5); + for width in [2usize, 3, 4] { + for tiled in [false, true] { + let mut s = GpuState::zero(n).unwrap(); + let t1 = std::time::Instant::now(); + s.run_tiled(&gates, width, tiled).unwrap(); + let dt = t1.elapsed().as_secs_f64(); + let fused = crate::quantum_sv::fuse(&gates, width); + let passes = plan_passes(n, &fused, tiled).len(); + println!("n={n} width={width} tiled={tiled}: {} gates, {} fused, {passes} passes, {dt:.3} s; norm {:.6}", gates.len(), fused.len(), s.norm2()); + } + } + } + } +} diff --git a/wai/wai-quantum/src/quantum_tableau.rs b/wai/wai-quantum/src/quantum_tableau.rs index bfd50df9c..d6bac6f5b 100644 --- a/wai/wai-quantum/src/quantum_tableau.rs +++ b/wai/wai-quantum/src/quantum_tableau.rs @@ -24,6 +24,23 @@ //! //! Signs follow Aaronson and Gottesman's `g` (the power of `i` when two Pauli //! letters multiply), evaluated a word at a time. +//! +//! **Noisy circuits** ([`frame_counts`], [`frame_expectations`]). Under Pauli +//! noise (depolarizing, dephasing, readout flips) a Clifford circuit is +//! sampled by Pauli frames, 64 shots to a machine word: +//! - One noiseless outcome from the tableau is the reference, and each shot +//! carries only the difference its noise makes. +//! - Frames start with random Z bits. Those leave `|0…0⟩` as it is, and +//! carried through the circuit they spread each shot uniformly over the +//! noiseless outcomes. +//! - Errors are placed by geometric gaps, so a channel costs its error rate, +//! not a draw a shot. +//! +//! An expectation value is the noiseless one times the frames' mean sign +//! against the string. Both readouts are the same bits at any thread count, +//! and agree with the exact density matrix on small circuits. On a 16-core +//! desktop processor, 100,000 full-width shots of a 10,000-qubit circuit with +//! 100,000 noisy gates take 1.6 s; at 1,000 qubits and 10,000 gates, 44 ms. /// The most qubits a tableau holds: the tableau is `4n²` bits, so 32768 /// qubits take 512 MiB, and the outcome subspace about as much again. @@ -58,6 +75,8 @@ pub enum TableauError { RepeatedQubit(u32), /// A Pauli factor that is not `X`, `Y`, `Z` or `I`. BadPauli(char), + /// A noise probability outside `[0, 1]` (as written). + BadProbability(String), } impl std::fmt::Display for TableauError { @@ -67,6 +86,7 @@ impl std::fmt::Display for TableauError { TableauError::QubitOutOfRange { qubit, n } => write!(f, "qubit {qubit} of a {n}-qubit tableau"), TableauError::RepeatedQubit(q) => write!(f, "qubit {q} named twice"), TableauError::BadPauli(c) => write!(f, "{c:?} is not a Pauli factor"), + TableauError::BadProbability(p) => write!(f, "{p} is not a probability"), } } } @@ -567,6 +587,373 @@ impl Sampler { } } +// --------------------------------------------------------------------------- +// Noisy shots by Pauli frames +// --------------------------------------------------------------------------- + +/// One step of a noisy Clifford circuit: a gate, or a Pauli channel. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FrameStep { + Gate(Clifford), + /// X, Y or Z on the qubit, each with probability `p/3`. + Depolarize1(u32, f64), + /// One of the fifteen non-identity Paulis on the pair, each `p/15`. + Depolarize2(u32, u32, f64), + /// Z on the qubit with probability `p`. + Dephase(u32, f64), +} + +/// Words of shots a qubit holds in one batch (64 shots a word). +const FRAME_WORDS: usize = 128; + +/// The gates of `steps`, checked, without the noise. +fn frame_gates(n: u32, steps: &[FrameStep]) -> Result, TableauError> { + let qubit = |q: u32| if q < n { Ok(()) } else { Err(TableauError::QubitOutOfRange { qubit: q, n }) }; + let prob = |p: f64| if (0.0..=1.0).contains(&p) { Ok(()) } else { Err(TableauError::BadProbability(p.to_string())) }; + let mut gates = Vec::new(); + for s in steps { + match *s { + FrameStep::Gate(g) => gates.push(g), + FrameStep::Depolarize1(q, p) | FrameStep::Dephase(q, p) => { + qubit(q)?; + prob(p)?; + } + FrameStep::Depolarize2(a, b, p) => { + qubit(a)?; + qubit(b)?; + if a == b { + return Err(TableauError::RepeatedQubit(a)); + } + prob(p)?; + } + } + } + Ok(gates) +} + +/// The next success, at least one trial after the last, in trials of +/// probability `p`; the logarithm is the crate's own, so the draws are the +/// same everywhere. +fn gap(p: f64, rng: &mut u64) -> u64 { + if p >= 1.0 { + return 1; + } + let u = 1.0 - (splitmix(rng) >> 11) as f64 * (1.0 / 9_007_199_254_740_992.0); + let g = (crate::repro::ln(u) / crate::repro::ln(1.0 - p)).floor(); + if g >= 1.8e19 { u64::MAX } else { g as u64 + 1 } +} + +/// The Pauli frames of one batch of shots: for qubit `q`, its X bits are +/// `x[q·w..(q+1)·w]` and its Z bits `z[…]`, one bit a shot. +struct Frames { + w: usize, + x: Vec, + z: Vec, +} + +impl Frames { + /// Frames for `n` qubits with every Z bit random: a Z on `|0⟩` changes + /// nothing, and carried through the circuit it makes each shot's + /// noiseless outcome a uniform draw from the outcome subspace. + fn gauge(n: u32, w: usize, rng: &mut u64) -> Frames { + let len = n as usize * w; + Frames { w, x: vec![0; len], z: (0..len).map(|_| splitmix(rng)).collect() } + } + + fn run(&self, q: u32) -> core::ops::Range { + let a = q as usize * self.w; + a..a + self.w + } + + /// Carry the frames through a gate: `P ↦ G·P·G†`, signs dropped. + fn gate(&mut self, g: Clifford) { + let w = self.w; + let (x, z) = (&mut self.x, &mut self.z); + match g { + Clifford::X(_) | Clifford::Y(_) | Clifford::Z(_) => {} + Clifford::H(q) => { + let r = q as usize * w..(q as usize + 1) * w; + x[r.clone()].swap_with_slice(&mut z[r]); + } + Clifford::S(q) | Clifford::Sdg(q) => { + let a = q as usize * w; + for i in a..a + w { + z[i] ^= x[i]; + } + } + Clifford::SX(q) | Clifford::SXdg(q) => { + let a = q as usize * w; + for i in a..a + w { + x[i] ^= z[i]; + } + } + Clifford::CX(c, t) => { + let (c, t) = (c as usize * w, t as usize * w); + for i in 0..w { + x[t + i] ^= x[c + i]; + z[c + i] ^= z[t + i]; + } + } + Clifford::CY(c, t) => { + let (c, t) = (c as usize * w, t as usize * w); + for i in 0..w { + let (xt, zt) = (x[t + i], z[t + i]); + z[c + i] ^= xt ^ zt; + x[t + i] ^= x[c + i]; + z[t + i] ^= x[c + i]; + } + } + Clifford::CZ(a, b) => { + let (a, b) = (a as usize * w, b as usize * w); + for i in 0..w { + z[a + i] ^= x[b + i]; + z[b + i] ^= x[a + i]; + } + } + Clifford::Swap(a, b) => { + let (a, b) = (a as usize * w, b as usize * w); + for i in 0..w { + x.swap(a + i, b + i); + z.swap(a + i, b + i); + } + } + } + } + + /// Multiply shot `s`'s frame by Pauli `p` (1 X, 2 Y, 3 Z) on `q`. + fn flip(&mut self, q: u32, p: u64, s: usize) { + let (i, b) = (q as usize * self.w + s / 64, 1u64 << (s % 64)); + if p != 3 { + self.x[i] ^= b; + } + if p >= 2 { + self.z[i] ^= b; + } + } + + /// Draw a channel's errors over the batch's shots and apply them. + fn channel(&mut self, step: FrameStep, rng: &mut u64) { + let shots = (64 * self.w) as u64; + let p = match step { + FrameStep::Gate(_) => return, + FrameStep::Depolarize1(_, p) | FrameStep::Depolarize2(_, _, p) | FrameStep::Dephase(_, p) => p, + }; + if p <= 0.0 { + return; + } + let mut at = 0u64; + loop { + at = at.saturating_add(gap(p, rng)); + if at > shots { + return; + } + let s = (at - 1) as usize; + match step { + FrameStep::Depolarize1(q, _) => self.flip(q, 1 + splitmix(rng) % 3, s), + FrameStep::Depolarize2(a, b, _) => { + let k = 1 + splitmix(rng) % 15; + if k >> 2 != 0 { + self.flip(a, k >> 2, s); + } + if k & 3 != 0 { + self.flip(b, k & 3, s); + } + } + FrameStep::Dephase(q, _) => self.flip(q, 3, s), + FrameStep::Gate(_) => unreachable!(), + } + } + } +} + +/// Run `f(batch)` for every batch of `shots`, on up to `threads` threads, and +/// return the results in batch order. A batch's stream depends only on +/// `(seed, batch)`. +fn frame_batches(shots: u64, w: usize, threads: usize, f: &(dyn Fn(u64, usize) -> R + Sync)) -> Vec { + let per = 64 * w as u64; + let batches = shots.div_ceil(per); + let size = |b: u64| ((shots - b * per).min(per)) as usize; + let threads = if cfg!(target_arch = "wasm32") { 1 } else { threads.clamp(1, batches.max(1) as usize) }; + if threads == 1 { + return (0..batches).map(|b| f(b, size(b))).collect(); + } + // A word-sized counter: some targets have no 64-bit atomics. + let next = std::sync::atomic::AtomicUsize::new(0); + let slots: Vec>> = (0..batches).map(|_| std::sync::Mutex::new(None)).collect(); + std::thread::scope(|sc| { + for _ in 0..threads { + sc.spawn(|| loop { + let i = next.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + if i as u64 >= batches { + break; + } + let b = i as u64; + *slots[i].lock().unwrap() = Some(f(b, size(b))); + }); + } + }); + slots.into_iter().map(|m| m.into_inner().unwrap().expect("every batch ran")).collect() +} + +fn batch_rng(seed: u64, batch: u64) -> u64 { + let mut s = seed ^ batch.wrapping_mul(0xD1B5_4A32_D192_ED03) ^ 0xF4A3_E5A5_0001; + splitmix(&mut s); + s +} + +/// Words a qubit holds per batch for `shots` shots. +fn frame_words(shots: u64) -> usize { + (shots.div_ceil(64) as usize).clamp(1, FRAME_WORDS) +} + +/// `shots` measurement outcomes of a noisy Clifford circuit from `|0…0⟩`, the +/// `measured` qubits read in the computational basis with each bit flipped +/// with probability `readout`, counted as [`Sampler::counts`] counts them. +/// +/// Pauli-frame sampling: one noiseless outcome from the tableau is the +/// reference, and each shot carries a Pauli frame (the difference its noise +/// makes), 64 shots to a machine word. Frames start with random Z bits, +/// which leave `|0…0⟩` unchanged and, carried through the circuit, spread each +/// shot uniformly over the noiseless outcomes. Noise is drawn by geometric +/// gaps between errors, so a channel costs its error rate, not a draw a shot. +/// A shot's outcome is the reference plus its frame's X bits on the measured +/// qubits. The counts have the law of independent noisy shots, and are the +/// same bits at any thread count. +#[allow(clippy::too_many_arguments)] +pub fn frame_counts(n: u32, steps: &[FrameStep], measured: &[u32], readout: f64, shots: u64, seed: u64, threads: usize) -> Result, u64)>, TableauError> { + let gates = frame_gates(n, steps)?; + if !(0.0..=1.0).contains(&readout) { + return Err(TableauError::BadProbability(readout.to_string())); + } + let t = Tableau::run(n, &gates)?; + for &q in measured { + if q >= n { + return Err(TableauError::QubitOutOfRange { qubit: q, n }); + } + } + let reference = t.support().sampler(measured).x0; + let m = measured.len(); + let mw = m.div_ceil(64); + let w = frame_words(shots); + let maps = frame_batches(shots, w, threads, &|b, size| { + let mut rng = batch_rng(seed, b); + let mut f = Frames::gauge(n, w, &mut rng); + for &s in steps { + match s { + FrameStep::Gate(g) => f.gate(g), + other => f.channel(other, &mut rng), + } + } + // Each shot's outcome bits, measured qubit j at bit j. + let mut out = vec![0u64; size * mw]; + for (j, &q) in measured.iter().enumerate() { + let r = if reference[j / 64] >> (j % 64) & 1 == 1 { !0u64 } else { 0 }; + let xs = &f.x[f.run(q)]; + for (k, &word) in xs.iter().enumerate() { + let mut bits = word ^ r; + while bits != 0 { + let s = k * 64 + bits.trailing_zeros() as usize; + bits &= bits - 1; + if s < size { + out[s * mw + j / 64] |= 1 << (j % 64); + } + } + } + } + if readout > 0.0 && m > 0 { + let trials = (size * m) as u64; + let mut at = 0u64; + loop { + at = at.saturating_add(gap(readout, &mut rng)); + if at > trials { + break; + } + let k = (at - 1) as usize; + let (s, j) = (k / m, k % m); + out[s * mw + j / 64] ^= 1 << (j % 64); + } + } + let mut map: std::collections::HashMap, u64> = std::collections::HashMap::new(); + for s in 0..size { + *map.entry(out[s * mw..(s + 1) * mw].to_vec()).or_insert(0) += 1; + } + map + }); + let mut total: std::collections::HashMap, u64> = std::collections::HashMap::new(); + for map in maps { + for (k, c) in map { + *total.entry(k).or_insert(0) += c; + } + } + let mut v: Vec<(Vec, u64)> = total.into_iter().collect(); + v.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0))); + Ok(v) +} + +/// Each Pauli string's expectation value in the noisy state, estimated from +/// `shots` frames, with its standard error. +/// +/// Under Pauli noise the state is a mixture of the noiseless state with Pauli +/// errors applied, so `⟨P⟩` is the noiseless `⟨P⟩` (`±1` or `0`, from the +/// tableau) times the average sign of `P` against the shots' frames. A string +/// whose noiseless value is `0` stays `0` exactly. +pub fn frame_expectations(n: u32, steps: &[FrameStep], strings: &[Vec<(u32, char)>], shots: u64, seed: u64, threads: usize) -> Result, TableauError> { + let gates = frame_gates(n, steps)?; + let t = Tableau::run(n, &gates)?; + let ideal: Vec = strings.iter().map(|s| t.expectation(s)).collect::>()?; + let shots = shots.max(1); + let w = frame_words(shots); + // Per batch, each string's count of shots whose frame anticommutes with it. + let flips = frame_batches(shots, w, threads, &|b, size| { + let mut rng = batch_rng(seed, b); + let mut f = Frames::gauge(n, w, &mut rng); + for &s in steps { + match s { + FrameStep::Gate(g) => f.gate(g), + other => f.channel(other, &mut rng), + } + } + strings + .iter() + .zip(&ideal) + .map(|(s, &e)| { + if e == 0 { + return 0u64; + } + let mut count = 0u64; + for k in 0..w { + let mut par = 0u64; + for &(q, c) in s { + let i = q as usize * w + k; + par ^= match c.to_ascii_uppercase() { + 'X' => f.z[i], + 'Z' => f.x[i], + 'Y' => f.x[i] ^ f.z[i], + _ => 0, + }; + } + let valid = if (k + 1) * 64 <= size { !0u64 } else if k * 64 >= size { 0 } else { (1u64 << (size - k * 64)) - 1 }; + count += u64::from((par & valid).count_ones()); + } + count + }) + .collect::>() + }); + Ok(ideal + .iter() + .enumerate() + .map(|(i, &e)| { + if e == 0 { + return (0.0, 0.0); + } + let flipped: u64 = flips.iter().map(|v| v[i]).sum(); + let mean = 1.0 - 2.0 * flipped as f64 / shots as f64; + let var = if shots > 1 { (1.0 - mean * mean) * shots as f64 / (shots - 1) as f64 } else { 0.0 }; + (f64::from(e) * mean, (var / shots as f64).sqrt()) + }) + .collect()) +} + #[cfg(test)] mod tests { use super::*; @@ -749,3 +1136,183 @@ mod tests { } } } + +#[cfg(all(test, feature = "quantum_noisy"))] +mod frame_tests { + use super::*; + use crate::gates::{complex, Gate}; + use crate::quantum_noisy::{DensityMatrix, NoiseModel}; + + fn next(s: &mut u64) -> u64 { + splitmix(s) + } + + fn gate(c: Clifford) -> Gate { + let (z, o, i) = (complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 1.0)); + match c { + Clifford::H(q) => Gate::h(q), + Clifford::S(q) => Gate::s(q), + Clifford::Sdg(q) => Gate::sdg(q), + Clifford::X(q) => Gate::x(q), + Clifford::Y(q) => Gate::y(q), + Clifford::Z(q) => Gate::z(q), + Clifford::SX(q) => Gate::sqrt_x(q), + Clifford::SXdg(q) => { + let (a, b) = (complex(0.5, -0.5), complex(0.5, 0.5)); + Gate { qubits: vec![q], matrix: vec![a, b, b, a] } + } + Clifford::CX(c, t) => Gate::cx(c, t), + Clifford::CY(c, t) => Gate { qubits: vec![c, t], matrix: vec![o, z, z, z, z, o, z, z, z, z, z, complex(0.0, -1.0), z, z, i, z] }, + Clifford::CZ(a, b) => Gate::cz(a, b), + Clifford::Swap(a, b) => Gate::swap(a, b), + } + } + + fn random_clifford(n: u32, len: usize, seed: u64) -> Vec { + let mut s = seed; + (0..len) + .map(|_| { + let a = (next(&mut s) % u64::from(n)) as u32; + let b = (a + 1 + (next(&mut s) % u64::from(n - 1)) as u32) % n; + match next(&mut s) % 12 { + 0 => Clifford::H(a), + 1 => Clifford::S(a), + 2 => Clifford::Sdg(a), + 3 => Clifford::SX(a), + 4 => Clifford::SXdg(a), + 5 => Clifford::X(a), + 6 => Clifford::Y(a), + 7 => Clifford::CX(a, b), + 8 => Clifford::CY(a, b), + 9 => Clifford::CZ(a, b), + 10 => Clifford::Swap(a, b), + _ => Clifford::H(b), + } + }) + .collect() + } + + /// The steps the other noisy methods apply: each gate, then its + /// depolarizing channel, then dephasing on each qubit it touched. + fn noisy(gates: &[Clifford], noise: &NoiseModel) -> Vec { + let mut out = Vec::new(); + for &g in gates { + out.push(FrameStep::Gate(g)); + let qs = gate(g).qubits; + if qs.len() == 2 { + out.push(FrameStep::Depolarize2(qs[0], qs[1], noise.depol2)); + } + for &q in &qs { + if qs.len() != 2 { + out.push(FrameStep::Depolarize1(q, noise.depol1)); + } + out.push(FrameStep::Dephase(q, noise.dephasing)); + } + } + out + } + + #[test] + fn noisy_counts_follow_the_density_matrix() { + for (n, seed) in [(3u32, 1u64), (4, 2), (5, 3)] { + let gates = random_clifford(n, 30, seed); + let noise = NoiseModel { depol1: 0.02, depol2: 0.05, dephasing: 0.01, damping: 0.0, readout: 0.03 }; + let matrices: Vec = gates.iter().map(|&g| gate(g)).collect(); + let p = DensityMatrix::run(&matrices, n, &noise).unwrap().probabilities(noise.readout); + let measured: Vec = (0..n).collect(); + let shots = 200_000u64; + let counts = frame_counts(n, &noisy(&gates, &noise), &measured, noise.readout, shots, seed, 4).unwrap(); + assert_eq!(counts.iter().map(|c| c.1).sum::(), shots); + let mut got = vec![0u64; 1 << n]; + for (bits, c) in &counts { + // Bit j is qubit j; the density matrix puts qubit q at bit n − 1 − q. + let x = (0..n).fold(0usize, |x, q| x | (((bits[0] >> q) & 1) as usize) << (n - 1 - q)); + got[x] = *c; + } + for x in 0..1usize << n { + let want = p[x] * shots as f64; + let sd = (want * (1.0 - p[x])).sqrt().max(1.0); + assert!((got[x] as f64 - want).abs() < 5.0 * sd, "n {n} x {x}: {} vs {want:.0}", got[x]); + } + } + } + + #[test] + fn noisy_expectations_follow_the_density_matrix() { + for (n, seed) in [(4u32, 7u64), (5, 8)] { + let gates = random_clifford(n, 40, seed); + let noise = NoiseModel { depol1: 0.01, depol2: 0.04, dephasing: 0.02, damping: 0.0, readout: 0.0 }; + let matrices: Vec = gates.iter().map(|&g| gate(g)).collect(); + let rho = DensityMatrix::run(&matrices, n, &noise).unwrap(); + // The noiseless stabilizers, so that the values are not all zero. + let t = Tableau::run(n, &gates).unwrap(); + let mut s = seed; + let mut strings: Vec> = Vec::new(); + while strings.len() < 12 { + let st: Vec<(u32, char)> = (0..n).filter_map(|q| match next(&mut s) % 4 { 0 => None, 1 => Some((q, 'X')), 2 => Some((q, 'Y')), _ => Some((q, 'Z')) }).collect(); + if !st.is_empty() && (t.expectation(&st).unwrap() != 0 || strings.len().is_multiple_of(3)) { + strings.push(st); + } + } + let est = frame_expectations(n, &noisy(&gates, &noise), &strings, 100_000, seed, 3).unwrap(); + for (st, &(v, se)) in strings.iter().zip(&est) { + let exact = rho.expectation(&[(1.0, st.clone())]); + assert!((v - exact).abs() < 5.0 * se + 1e-9, "n {n} {st:?}: {v} ± {se} vs {exact}"); + } + } + } + + #[test] + fn frames_are_the_same_bits_at_any_thread_count() { + let n = 30; + let gates = random_clifford(n, 300, 5); + let noise = NoiseModel { depol1: 0.01, depol2: 0.02, dephasing: 0.01, damping: 0.0, readout: 0.02 }; + let steps = noisy(&gates, &noise); + let measured: Vec = (0..n).step_by(3).collect(); + // A shot count that leaves a partial word and a partial batch. + let one = frame_counts(n, &steps, &measured, 0.02, 20_011, 9, 1).unwrap(); + assert_eq!(one.iter().map(|c| c.1).sum::(), 20_011); + assert_eq!(frame_counts(n, &steps, &measured, 0.02, 20_011, 9, 5).unwrap(), one); + let strings = vec![vec![(0, 'Z'), (3, 'X')], vec![(7, 'Y')]]; + assert_eq!(frame_expectations(n, &steps, &strings, 9_999, 2, 1).unwrap(), frame_expectations(n, &steps, &strings, 9_999, 2, 4).unwrap()); + } + + /// Times at scale; run with `--ignored --nocapture`. + #[test] + #[ignore] + fn frame_timings_at_scale() { + let noise = NoiseModel { depol1: 1e-4, depol2: 1e-3, dephasing: 1e-4, damping: 0.0, readout: 1e-3 }; + for (n, len, shots) in [(1000u32, 10_000usize, 100_000u64), (10_000, 100_000, 100_000)] { + let gates = random_clifford(n, len, 11); + let steps = noisy(&gates, &noise); + let measured: Vec = (0..n).collect(); + let t0 = std::time::Instant::now(); + let counts = frame_counts(n, &steps, &measured, noise.readout, shots, 1, 8).unwrap(); + let tc = t0.elapsed(); + let t1 = std::time::Instant::now(); + let e = frame_expectations(n, &steps, &[vec![(0, 'Z')], vec![(1, 'X'), (2, 'Z')]], 1_000_000, 2, 8).unwrap(); + println!("n={n} gates={len} steps={}: {shots} full-width shots in {tc:?} ({} distinct); 2 expectations from 1e6 shots in {:?}: {e:?}", steps.len(), counts.len(), t1.elapsed()); + } + } + + #[test] + fn a_wide_noisy_ghz_state_mostly_agrees() { + let n = 2000; + let mut gates = vec![Clifford::H(0)]; + gates.extend((0..n - 1).map(|q| Clifford::CX(q, q + 1))); + let noise = NoiseModel { depol1: 0.0, depol2: 1e-4, dephasing: 0.0, damping: 0.0, readout: 0.0 }; + let steps = noisy(&gates, &noise); + let measured: Vec = (0..n).collect(); + let counts = frame_counts(n, &steps, &measured, 0.0, 2000, 1, 4).unwrap(); + let words = n.div_ceil(64) as usize; + let all = |one: bool| -> Vec { (0..words).map(|w| if !one { 0 } else if (w + 1) * 64 <= n as usize { !0 } else { (1u64 << (n as usize % 64)) - 1 }).collect() }; + let clean: u64 = counts.iter().filter(|(b, _)| *b == all(false) || *b == all(true)).map(|c| c.1).sum(); + // About 1999 pair channels at 1e-4, of which 12/15 flip some bit: + // e^(−0.16) of the shots stay clean. + let f = clean as f64 / 2000.0; + assert!((f - (-0.16f64).exp()).abs() < 0.05, "{f}"); + // ⟨Z₀ Z_{n−1}⟩: flipped by an X or Y on the chain between them. + let e = frame_expectations(n, &steps, &[vec![(0, 'Z'), (n - 1, 'Z')]], 20_000, 3, 4).unwrap(); + assert!(e[0].0 > 0.8 && e[0].0 < 1.0, "{e:?}"); + } +} diff --git a/wai/wai-quantum/src/quantum_tn.rs b/wai/wai-quantum/src/quantum_tn.rs index 28631b6c3..36918fc21 100644 --- a/wai/wai-quantum/src/quantum_tn.rs +++ b/wai/wai-quantum/src/quantum_tn.rs @@ -72,11 +72,14 @@ //! essentially free: the same cost at a cap 16 times smaller than the //! unsliced intermediate. Far below the natural size it is not: at `m = 14` //! with a `2³⁰` cap, a plan costs about twelve times its unsliced cost. -//! - **The executor is scalar.** It runs about a billion complex multiply-adds -//! per second per core, threaded over slices as memory allows, with no SIMD -//! kernels or GPU. -//! Plans worth `10¹³` operations or more are for planning and verification -//! arithmetic here, not for contraction. +//! - **The executor is a blocked, threaded loop, without vector kernels or a +//! GPU.** Each pairwise contraction is a complex matrix product blocked for +//! the cache, its rows shared between threads (slices run in parallel +//! instead when there are several). Each output adds its terms in the same +//! order however the work is split, so the result is the same bits. On a +//! 16-core desktop processor it runs about 25 billion complex multiply-adds a +//! second on large products. Plans worth `10¹⁴` operations or more are for +//! planning and verification arithmetic here, not for contraction. //! - **Networks are simple graphs.** Every index joins at most two tensors. //! Diagonal-gate hyperedges are not exploited. @@ -276,25 +279,73 @@ pub fn contract(a: &Tensor, b: &Tensor, dims: &[usize], open: &[u32]) -> Tensor let ad = permute(a, &ao, dims); let bd = permute(b, &bo, dims); let mut out = vec![C::ZERO; m * n]; - for i in 0..m { - let row = &mut out[i * n..(i + 1) * n]; - for kk in 0..k { - let x = ad[i * k + kk]; - if x.re == 0.0 && x.im == 0.0 { - continue; - } - let brow = &bd[kk * n..(kk + 1) * n]; - for (o, y) in row.iter_mut().zip(brow) { - o.re += x.re * y.re - x.im * y.im; - o.im += x.re * y.im + x.im * y.re; - } - } - } + matmul_into(&mut out, &ad, &bd, m, k, n); let mut inds = a_rem; inds.extend(b_rem); Tensor { inds, data: out } } +/// Columns and inner terms a block of [`matmul_into`] holds: a block of `b` +/// (`KB × JB` complex values, 64 KiB) stays in cache while every row of the +/// block uses it. +const JB: usize = 256; +const KB: usize = 16; + +#[cfg(not(target_arch = "wasm32"))] +std::thread_local! { + /// Set inside callers that already run contractions in parallel. + static ONE_THREAD: core::cell::Cell = const { core::cell::Cell::new(false) }; +} + +/// `out (m × n) += a (m × k) · b (k × n)`, row-major. Each output element +/// adds its terms one at a time in `k` order (a zero factor of `a` skipped), +/// so blocking for the cache, and splitting rows between threads, give the +/// unblocked loop's bits. +fn matmul_into(out: &mut [C], a: &[C], b: &[C], m: usize, k: usize, n: usize) { + let rows = |out: &mut [C], i0: usize| { + let mrows = out.len() / n.max(1); + for j0 in (0..n).step_by(JB) { + let j1 = (j0 + JB).min(n); + for k0 in (0..k).step_by(KB) { + let k1 = (k0 + KB).min(k); + for i in 0..mrows { + let row = &mut out[i * n + j0..i * n + j1]; + for kk in k0..k1 { + let x = a[(i0 + i) * k + kk]; + if x.re == 0.0 && x.im == 0.0 { + continue; + } + let brow = &b[kk * n + j0..kk * n + j1]; + for (o, y) in row.iter_mut().zip(brow) { + o.re += x.re * y.re - x.im * y.im; + o.im += x.re * y.im + x.im * y.re; + } + } + } + } + } + }; + #[cfg(not(target_arch = "wasm32"))] + { + let threads = if ONE_THREAD.with(|o| o.get()) { 1 } else { std::thread::available_parallelism().map_or(1, |t| t.get()) }; + // Worth threading past a few million multiply-adds, with rows to share. + let threads = threads.min(m / 4).max(1); + if threads > 1 && (m as u64) * (k as u64) * (n as u64) >= 1 << 22 && n > 0 { + let per = m.div_ceil(threads); + std::thread::scope(|sc| { + for (c, chunk) in out.chunks_mut(per * n).enumerate() { + let rows = &rows; + sc.spawn(move || rows(chunk, c * per)); + } + }); + return; + } + } + if n > 0 { + rows(out, 0); + } +} + // --------------------------------------------------------------------------- // Contraction paths // --------------------------------------------------------------------------- @@ -1288,7 +1339,12 @@ pub fn contract_plan(net: &Network, plan: &Plan) -> Result { for (c, chunk) in chunks { let run = &run; let base = c * count.div_ceil(threads); + let inner_one = threads > 1; sc.spawn(move || { + // Slices in parallel: each slice's contractions on its own thread. + if inner_one { + ONE_THREAD.with(|o| o.set(true)); + } for (k, slot) in chunk.iter_mut().enumerate() { *slot = Some(run(base + k)); } @@ -1320,6 +1376,62 @@ pub fn contract_plan(net: &Network, plan: &Plan) -> Result { pub(crate) mod tests { use super::*; + /// The plain i-k-j loop the executor began with. + fn matmul_plain(a: &[C], b: &[C], m: usize, k: usize, n: usize) -> Vec { + let mut out = vec![C::ZERO; m * n]; + for i in 0..m { + let row = &mut out[i * n..(i + 1) * n]; + for kk in 0..k { + let x = a[i * k + kk]; + if x.re == 0.0 && x.im == 0.0 { + continue; + } + for (o, y) in row.iter_mut().zip(&b[kk * n..(kk + 1) * n]) { + o.re += x.re * y.re - x.im * y.im; + o.im += x.re * y.im + x.im * y.re; + } + } + } + out + } + + /// Times; run with `--ignored --nocapture`. + #[test] + #[ignore] + fn product_timings() { + let mut rng = Rng(4); + for (m, k, n) in [(1024usize, 1024usize, 1024usize), (4096, 256, 4096), (64, 4096, 4096)] { + let a: Vec = (0..m * k).map(|_| C::new(rng.unit() - 0.5, rng.unit() - 0.5)).collect(); + let b: Vec = (0..k * n).map(|_| C::new(rng.unit() - 0.5, rng.unit() - 0.5)).collect(); + let t0 = std::time::Instant::now(); + let want = matmul_plain(&a, &b, m, k, n); + let tp = t0.elapsed().as_secs_f64(); + let mut got = vec![C::ZERO; m * n]; + let t1 = std::time::Instant::now(); + matmul_into(&mut got, &a, &b, m, k, n); + let tb = t1.elapsed().as_secs_f64(); + let macs = (m * k * n) as f64; + println!("{m}x{k}x{n}: plain {tp:.3} s ({:.2} G/s), blocked+threaded {tb:.3} s ({:.2} G/s); same {}", macs / tp / 1e9, macs / tb / 1e9, got == want); + } + } + + #[test] + fn blocked_threaded_products_are_the_plain_loops_bits() { + let mut rng = Rng(3); + for (m, k, n) in [(1usize, 1usize, 1usize), (3, 17, 5), (64, 64, 64), (300, 40, 513), (1024, 33, 700), (9, 1000, 2)] { + let mut a: Vec = (0..m * k).map(|_| C::new(rng.unit() - 0.5, rng.unit() - 0.5)).collect(); + // Some exact zeros, which both skip. + for x in a.iter_mut().step_by(7) { + *x = C::ZERO; + } + let b: Vec = (0..k * n).map(|_| C::new(rng.unit() - 0.5, rng.unit() - 0.5)).collect(); + let want = matmul_plain(&a, &b, m, k, n); + let mut got = vec![C::ZERO; m * n]; + matmul_into(&mut got, &a, &b, m, k, n); + assert!(got.iter().zip(&want).all(|(x, y)| x.re.to_bits() == y.re.to_bits() && x.im.to_bits() == y.im.to_bits()), "{m}×{k}×{n}"); + } + } + /// A random unitary on `k` qubits from the QR of a random matrix /// (Gram–Schmidt on columns): enough to exercise the contractions. #[allow(clippy::needless_range_loop)] diff --git a/wai/wai-quantum/src/quantum_toolchain.rs b/wai/wai-quantum/src/quantum_toolchain.rs index a3f544ff7..978c8021a 100644 --- a/wai/wai-quantum/src/quantum_toolchain.rs +++ b/wai/wai-quantum/src/quantum_toolchain.rs @@ -620,9 +620,22 @@ pub fn to_qasm(c: &Circuit) -> String { (BaseGate::Y, [c0]) => format!("cy q[{c0}],q[{t}];"), (BaseGate::Z, [c0]) => format!("cz q[{c0}],q[{t}];"), (BaseGate::P, [c0]) => format!("cp(2*pi/{}) q[{c0}],q[{t}];", 1u64 << g.param), + (BaseGate::H, [c0]) => format!("ch q[{c0}],q[{t}];"), (BaseGate::X, [a, b]) => format!("ccx q[{a}],q[{b}],q[{t}];"), (BaseGate::Z, [a, b]) => format!("ccz q[{a}],q[{b}],q[{t}];"), - (base, ctrls) => format!("// unmapped: {base:?} controls={ctrls:?} target={t}"), + // A controlled identity is the identity. + (BaseGate::I, _) => continue, + // Every other controlled gate in OpenQASM 3's modifier form, which + // `from_qasm` reads back: nothing is left out. + (base, ctrls) => { + let name = match base { + BaseGate::P => format!("p(2*pi/{})", 1u64 << g.param), + b => format!("{b:?}").to_lowercase(), + }; + let m = if ctrls.len() == 1 { "ctrl".to_string() } else { format!("ctrl({})", ctrls.len()) }; + let qs: Vec = ctrls.iter().chain([&t]).map(|q| format!("q[{q}]")).collect(); + format!("{m} @ {name} {};", qs.join(",")) + } }; s.push_str(&line); s.push('\n'); @@ -689,6 +702,11 @@ pub fn from_qasm(src: &str) -> Result { if line.contains("measure") { continue; } + // Gate modifiers: `ctrl @`, `ctrl(n) @`, `negctrl @`, `negctrl(n) @`. + if let Some((polarity, call)) = modifiers(&line)? { + push_modified(&mut c, &polarity, &call)?; + continue; + } let head: String = line.chars().take_while(|c| c.is_ascii_alphanumeric() || *c == '_').collect(); let head = head.as_str(); match head { @@ -805,6 +823,92 @@ pub fn from_qasm(src: &str) -> Result { Ok(c) } +/// The polarities of a line's gate modifiers, in order (`true`: a control on +/// |1⟩, `false`: on |0⟩), and the gate call after them; `None` when the line +/// has none. +fn modifiers(line: &str) -> Result, String)>, String> { + let mut rest = line.trim_start(); + let mut polarity = Vec::new(); + loop { + let (on, after) = if let Some(a) = rest.strip_prefix("negctrl") { + (false, a) + } else if let Some(a) = rest.strip_prefix("ctrl") { + (true, a) + } else { + break; + }; + let after = after.trim_start(); + let (count, after) = match after.strip_prefix('(') { + Some(a) => { + let end = a.find(')').ok_or("unterminated modifier argument")?; + let n: usize = a[..end].trim().parse().map_err(|_| format!("the modifier's count `{}` is not a whole number", a[..end].trim()))?; + (n, a[end + 1..].trim_start()) + } + None => (1, after), + }; + let after = after.strip_prefix('@').ok_or("expected `@` after a gate modifier")?; + if count == 0 { + return Err("a control modifier adds at least one control".into()); + } + polarity.extend(std::iter::repeat_n(on, count)); + rest = after.trim_start(); + } + Ok((!polarity.is_empty()).then(|| (polarity, rest.to_string()))) +} + +/// A gate under control modifiers: the added controls are the first qubits +/// named, in the modifiers' order, and a control on |0⟩ is a control between +/// two `x`. Only gates read here exactly, global phase and all, may be +/// controlled, since a control makes a global phase observable: `rz`, `rx`, +/// `ry`, `sx` and `u`, exact here up to their global phase, are refused. +fn push_modified(c: &mut Circuit, polarity: &[bool], call: &str) -> Result<(), String> { + let head: String = call.chars().take_while(|c| c.is_ascii_alphanumeric() || *c == '_').collect(); + // (the base gate, the controls the name already has, whether it takes an angle) + let (base, inner, angle) = match head.as_str() { + "x" => (BaseGate::X, 0, false), + "y" => (BaseGate::Y, 0, false), + "z" => (BaseGate::Z, 0, false), + "h" => (BaseGate::H, 0, false), + "s" => (BaseGate::S, 0, false), + "sdg" => (BaseGate::Sdg, 0, false), + "t" => (BaseGate::T, 0, false), + "tdg" => (BaseGate::Tdg, 0, false), + "p" | "u1" | "phase" => (BaseGate::P, 0, true), + "cx" | "cnot" => (BaseGate::X, 1, false), + "cy" => (BaseGate::Y, 1, false), + "cz" => (BaseGate::Z, 1, false), + "ch" => (BaseGate::H, 1, false), + "cp" | "cu1" | "cphase" => (BaseGate::P, 1, true), + "ccx" | "toffoli" => (BaseGate::X, 2, false), + "ccz" => (BaseGate::Z, 2, false), + other => { + return Err(format!( + "a control modifier on '{other}' is not supported (supported under one: x y z h s sdg t tdg p cx cy cz ch cp ccx ccz)" + )); + } + }; + let qs = parse_qubits(call); + let want = polarity.len() + inner + 1; + if qs.len() != want { + return Err(format!("'{head}' under {} added control(s) takes {want} qubits, given {}", polarity.len(), qs.len())); + } + let (controls, target) = (qs[..want - 1].to_vec(), qs[want - 1]); + distinct_qubits(&controls, target)?; + let flip: Vec = controls.iter().zip(polarity).filter(|(_, on)| !**on).map(|(q, _)| *q).collect(); + for &q in &flip { + c.x(q); + } + if angle { + push_turn(c, &controls, target, parse_angle(&arg_n(call, 0)?)?)?; + } else { + c.ops.push(Gate { base, controls, target, param: 0 }); + } + for &q in &flip { + c.x(q); + } + Ok(()) +} + /// Refuse a gate that names one qubit twice, a control repeated or equal to /// the target, as an invalid circuit in every gateset. fn distinct_qubits(controls: &[u8], target: u8) -> Result<(), String> { @@ -1220,7 +1324,9 @@ mod tests { #[test] fn qasm_round_trip() { - for c in [bell(), ghz(3), Circuit::qft(4), grover(3, 5)] { + // Grover's search past three qubits uses a Z with three or more + // controls, which the export once wrote as a comment and lost. + for c in [bell(), ghz(3), Circuit::qft(4), grover(3, 5), grover(5, 0b10110), grover(7, 77)] { let qasm = to_qasm(&c); let back = from_qasm(&qasm).expect("reparse"); // same reconstructed state ⇒ same circuit semantics @@ -1232,6 +1338,46 @@ mod tests { } } + #[test] + fn every_controlled_gate_is_exported_and_read_back_as_it_was() { + use BaseGate::*; + for base in [X, Y, Z, H, S, Sdg, T, Tdg, P] { + for nc in 0..=5u8 { + let mut c = Circuit::new(7); + c.ops.push(Gate { base, controls: (0..nc).collect(), target: 6, param: if base == P { 3 } else { 0 } }); + let qasm = to_qasm(&c); + assert!(!qasm.contains("//"), "{qasm}"); + assert_eq!(from_qasm(&qasm).unwrap().ops, c.ops, "{qasm}"); + } + } + } + + #[test] + fn control_modifiers_are_read_with_their_polarity() { + let c = from_qasm("qubit[4] q;\nnegctrl @ ctrl(2) @ z q[0], q[1], q[2], q[3];\nctrl @ cp(pi/4) q[1], q[2], q[0];").unwrap(); + let x0 = Gate { base: BaseGate::X, controls: vec![], target: 0, param: 0 }; + assert_eq!( + c.ops, + vec![ + x0.clone(), + Gate { base: BaseGate::Z, controls: vec![0, 1, 2], target: 3, param: 0 }, + x0, + Gate { base: BaseGate::P, controls: vec![1, 2], target: 0, param: 3 }, + ] + ); + // A global phase a control would expose, a count of nothing, and a + // wrong number of qubits are refused. + for (src, needle) in [ + ("qubit[2] q;\nctrl @ rz(pi/2) q[0], q[1];", "not supported"), + ("qubit[2] q;\nctrl(0) @ x q[0], q[1];", "at least one"), + ("qubit[3] q;\nctrl @ x q[0], q[1], q[2];", "takes 2 qubits"), + ("qubit[2] q;\nctrl @ x q[1], q[1];", "equals target"), + ] { + let e = from_qasm(src).unwrap_err(); + assert!(e.contains(needle), "{src}: {e}"); + } + } + #[test] fn qasm_imports_foreign_circuit() { let src = "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[2] q;\nh q[0];\ncx q[0], q[1];\n"; diff --git a/wai/wai-quantum/src/repro.rs b/wai/wai-quantum/src/repro.rs index 687e6a10e..8867e8b14 100644 --- a/wai/wai-quantum/src/repro.rs +++ b/wai/wai-quantum/src/repro.rs @@ -38,28 +38,28 @@ pub use trig::sin_cos; // Natural logarithm // --------------------------------------------------------------------------- -#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy"))] +#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy", feature = "quantum_tableau"))] const LN2_HI: f64 = 6.931_471_803_691_238_164_90e-1; -#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy"))] +#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy", feature = "quantum_tableau"))] const LN2_LO: f64 = 1.908_214_929_270_587_700_02e-10; -#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy"))] +#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy", feature = "quantum_tableau"))] const LG1: f64 = 6.666_666_666_666_735_130e-1; -#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy"))] +#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy", feature = "quantum_tableau"))] const LG2: f64 = 3.999_999_999_940_941_908e-1; -#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy"))] +#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy", feature = "quantum_tableau"))] const LG3: f64 = 2.857_142_874_366_239_149e-1; -#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy"))] +#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy", feature = "quantum_tableau"))] const LG4: f64 = 2.222_219_843_214_978_396e-1; -#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy"))] +#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy", feature = "quantum_tableau"))] const LG5: f64 = 1.818_357_216_161_805_012e-1; -#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy"))] +#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy", feature = "quantum_tableau"))] const LG6: f64 = 1.531_383_769_920_937_332e-1; -#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy"))] +#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy", feature = "quantum_tableau"))] const LG7: f64 = 1.479_819_860_511_658_591e-1; /// The natural logarithm, within an ulp of the true value: `−∞` at zero, NaN /// below it. -#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy"))] +#[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy", feature = "quantum_tableau"))] pub fn ln(x: f64) -> f64 { let mut bits = x.to_bits(); let mut hx = (bits >> 32) as u32; @@ -359,7 +359,7 @@ mod tests { }) } - #[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy"))] + #[cfg(any(feature = "quantum_vml", feature = "quantum_kernel", feature = "quantum_resource", feature = "quantum_frame", feature = "quantum_tn", feature = "quantum_gbs", feature = "quantum_arch", feature = "quantum_qio", feature = "quantum_dmrg", feature = "quantum_noisy", feature = "quantum_tableau"))] #[test] fn ln_is_within_an_ulp_of_the_platform() { for x in inputs().filter(|&x| x > 0.0) { diff --git a/wai/wai-rs/Cargo.lock b/wai/wai-rs/Cargo.lock index 06f163452..47f996cd2 100644 --- a/wai/wai-rs/Cargo.lock +++ b/wai/wai-rs/Cargo.lock @@ -4310,7 +4310,7 @@ version = "0.1.0" [[package]] name = "wai-quantum" -version = "0.6.3" +version = "0.6.4" dependencies = [ "blake3", "ed25519-dalek", diff --git a/wai/wai-web/Cargo.lock b/wai/wai-web/Cargo.lock index c462a4e7d..2f02cf70e 100644 --- a/wai/wai-web/Cargo.lock +++ b/wai/wai-web/Cargo.lock @@ -1241,7 +1241,7 @@ version = "0.1.0" [[package]] name = "wai-quantum" -version = "0.6.3" +version = "0.6.4" dependencies = [ "blake3", "ed25519-dalek",