diff --git a/optimized_solutions/challenge-08/README.md b/optimized_solutions/challenge-08/README.md new file mode 100644 index 0000000..515f83f --- /dev/null +++ b/optimized_solutions/challenge-08/README.md @@ -0,0 +1,57 @@ +# Task 08 — bounded TensorCircuit sampling batches + +## Take-home insight + +**Keep the public expert’s TensorCircuit circuit and all 8192 +`perfect_sampling` trajectories, but execute the mapped sampler in contiguous +256-shot blocks.** A single 8192-shot `vmap` puts the shot axis into every XLA +contraction intermediate; bounding that axis is the only material change and +reduces the peak live tensor-network workload without changing the sampling +algorithm. + +## Factor attribution + +| Factor | Full-workload result | Decision | +| --- | ---: | --- | +| 512-shot blocks | 50.843 s single screen | Feasible, not best | +| **256-shot blocks** | **44.028 s single screen** | **Promote** | +| 128-shot blocks | 55.182 s single screen | Dispatch overhead | + +![Task 08 factor attribution](factor-ablation.svg) + +*Figure — Panel a keeps the complete sufficient-memory five-pair comparison +and makes clear that its confidence interval includes `1x`. Panels b–c +normalize the 256-shot screen to `1.0x` and show the additional runtime from +512- and 128-shot blocks. The 256-shot setting is retained for bounded +execution and the best full-workload chunk-size screen, not a confirmed +end-to-end speedup.* + +## What the factors mean + +- **Bounded mapped batch:** the same cached TensorCircuit + `K.jit(K.vmap(perfect_sampling))` function consumes 32 contiguous slices + instead of the complete status matrix at once. Every seeded status row is + still consumed exactly once and in the original order. +- **Chunk size:** 512 leaves larger contraction intermediates live; 128 adds + excess host dispatch. A 256-shot block is the measured balance for this + workload. + +## Complete canonical result + +A sufficient-memory, same-node, six-CPU-affinity five-pair session ran the +complete 8192-shot workload for both implementations: + +| Metric | Public expert | 256-shot candidate | +| --- | ---: | ---: | +| Passing runs | 5/5 | 5/5 | +| Mean runtime | 126.675 s | 123.188 s | +| Pair wins | 2/5 | 3/5 | +| Mean pairwise speedup | — | 1.045x | +| 95% Student-t interval | — | [0.818, 1.273] | + +The interval crosses 1.0, so there is **no confirmed runtime speedup**. The +promoted benefit is bounded peak memory: under tighter allocations the +monolithic mapped batch can exhaust memory, while the 256-shot implementation +completes the unchanged workload. The exact memory threshold is hardware and +contraction-path dependent; the full allocation study remains in the benchmark +research repository. diff --git a/optimized_solutions/challenge-08/factor-ablation.svg b/optimized_solutions/challenge-08/factor-ablation.svg new file mode 100644 index 0000000..00dcbfc --- /dev/null +++ b/optimized_solutions/challenge-08/factor-ablation.svg @@ -0,0 +1,607 @@ + + + + + + + + image/svg+xml + + + Matplotlib v3.10.8, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 256-shot + 123.19 s + + + + + + + + + + Public expert + 126.68 s + + + + + + + + + + + + + + + + + + 0.0 + + + + + + + + + + + + + 0.2 + + + + + + + + + + + + + 0.4 + + + + + + + + + + + + + 0.6 + + + + + + + + + + + + + 0.8 + + + + + + + + + + + + + 1.0 + + + + Runtime normalized to recommended + + + + + + + + + + + + + + + + paired mean 1.045×; CI [0.818, 1.273] + + + a + + + 1.000× + + + 1.028× + + + Complete 8192 shots + + + + + + + + + + + + + + + + + + 256 shots + 44.03 s + + + + + + + + + + 512 shots + 50.84 s + + + + + + + + + + + + + + + 0.0 + + + + + + + + + + + + + 0.2 + + + + + + + + + + + + + 0.4 + + + + + + + + + + + + + 0.6 + + + + + + + + + + + + + 0.8 + + + + + + + + + + + + + 1.0 + + + + + + + + + + + + + 1.2 + + + + + + + + + + + + + + + + + single full-workload screen + + + b + + + 1.000× + + + 1.155× + + + 256-shot chunks → 512-shot chunks + + + + + + + + + + + + + + + + + + 256 shots + 44.03 s + + + + + + + + + + 128 shots + 55.18 s + + + + + + + + + + + + + + + 0.0 + + + + + + + + + + + + + 0.2 + + + + + + + + + + + + + 0.4 + + + + + + + + + + + + + 0.6 + + + + + + + + + + + + + 0.8 + + + + + + + + + + + + + 1.0 + + + + + + + + + + + + + 1.2 + + + + + + + + + + + + + + + + + single full-workload screen + + + c + + + 1.000× + + + 1.253× + + + 256-shot chunks → 128-shot chunks + + + + + + + Task 08 factor ablations — 256-shot chunks minimize the screen; full-run speedup is unconfirmed + + + Panel a: five complete 8192-shot pairs; paired 95% CI includes 1. Panels b–c: one full-workload screen per chunk size. + + + + + + + + + + + + + + diff --git a/optimized_solutions/challenge-08/solution_8_bounded_batches.py b/optimized_solutions/challenge-08/solution_8_bounded_batches.py new file mode 100644 index 0000000..e65888c --- /dev/null +++ b/optimized_solutions/challenge-08/solution_8_bounded_batches.py @@ -0,0 +1,115 @@ +""" +Task Suite Problem 8: 7x7 mixed-axis grid sampling. + +The solution builds the TensorCircuit tensor network directly and samples from it +without constructing the full 2^49 statevector. +""" + +import numpy as np + +import tensorcircuit as tc + +K = tc.set_backend("jax") +tc.set_dtype("complex64") +tc.set_contractor("omeco-4-4") + + +def ry_angle(row, col, config): + return ( + config["ry_offset"] + + config["ry_row_sin_scale"] + * np.sin(config["ry_row_sin_frequency"] * (row + 1)) + + config["ry_col_cos_scale"] + * np.cos(config["ry_col_cos_frequency"] * (col + 1)) + + config["ry_diag_sin_scale"] + * np.sin(config["ry_diag_sin_frequency"] * (row + col + 2)) + ) + + +def rzz_angle(row, col, edge_index, config): + return ( + config["rzz_offset"] + + config["rzz_edge_sin_scale"] + * np.sin(config["rzz_edge_sin_frequency"] * (edge_index + 1)) + + config["rzz_site_cos_scale"] + * np.cos(config["rzz_site_cos_frequency"] * (2 * row + col + 1)) + ) + + +def rxx_angle(row, col, edge_index, config): + return ( + config["rxx_offset"] + + config["rxx_edge_cos_scale"] + * np.cos(config["rxx_edge_cos_frequency"] * (edge_index + 1)) + + config["rxx_site_sin_scale"] + * np.sin(config["rxx_site_sin_frequency"] * (row + 2 * col + 1)) + ) + + +def rx_angle(row, col, config): + return ( + config["rx_offset"] + + config["rx_row_cos_scale"] + * np.cos(config["rx_row_cos_frequency"] * (row + 1)) + - config["rx_col_sin_scale"] + * np.sin(config["rx_col_sin_frequency"] * (col + 1)) + + config["rx_diag_cos_scale"] + * np.cos(config["rx_diag_cos_frequency"] * (row + col + 2)) + ) + + +def build_circuit(config): + n_side = config["grid_side"] + circuit = tc.Circuit(config["n_qubits"]) + + for row in range(n_side): + for col in range(n_side): + qubit = row * n_side + col + circuit.ry(qubit, theta=ry_angle(row, col, config)) + + edge_index = 0 + for row in range(n_side): + for col in range(n_side - 1): + left = row * n_side + col + right = left + 1 + circuit.rzz(left, right, theta=rzz_angle(row, col, edge_index, config)) + edge_index += 1 + + edge_index = 0 + for row in range(n_side - 1): + for col in range(n_side): + left = row * n_side + col + right = (row + 1) * n_side + col + circuit.rxx(left, right, theta=rxx_angle(row, col, edge_index, config)) + edge_index += 1 + + for row in range(n_side): + for col in range(n_side): + qubit = row * n_side + col + circuit.rx(qubit, theta=rx_angle(row, col, config)) + + return circuit + + +def run_solution(config): + circuit = build_circuit(config) + + rng = np.random.default_rng(2033) + status = K.convert_to_tensor( + rng.random((config["n_samples"], config["n_qubits"]), dtype=np.float32) + ) + + def sample_one(seed): + sample, _ = circuit.perfect_sampling(seed) + return sample + + sample_batch = K.jit(K.vmap(sample_one)) + chunk_size = 256 + samples = [ + np.asarray( + K.numpy(sample_batch(status[start : start + chunk_size])), + dtype=np.int32, + ) + for start in range(0, config["n_samples"], chunk_size) + ] + return {"samples": np.concatenate(samples, axis=0)}