File size: 4,500 Bytes
9e551a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# Tensor Roll — Recursive CUDA-Q Model Quantizer
# Copyright (C) 2026 SnapKitty Collective
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Real CUDA-Q kernels for the tensor_roll family.

Target: NVIDIA CUDA-Q Python API (>= 0.8). Execute with a simulator
target (``cudaq.set_target("qpp-cpu")``) or on QPU/GPU hardware.

These kernels are NOT executed by the tensor-roll CLI on machines without
the CUDA-Q toolchain (see docs/BOUNDARIES.md). The classical state-vector
simulator in tensor_roll/qsim.py mirrors this family's semantics so the
pipeline is measurable everywhere; every qsim report is labeled
``qsim-classical`` and never claims QPU execution.

Conceptual mapping (classical -> quantum descent):
    C = A x B            (CUDA GEMM, classical baseline)
    TensorRoll(A, B)     (dispatch boundary in tensor_roll/core.py)
      |- classical region -> CUDA GEMM
      -- candidate region -> tensor_roll kernels below -> measurement
                             -> classical reconstruction
"""

try:
    import cudaq
    from cudaq import spin
    _HAS_CUDAQ = True
except ImportError:  # pragma: no cover - toolchain absent on most machines
    _HAS_CUDAQ = False


if _HAS_CUDAQ:

    @cudaq.kernel
    def tensor_roll_encode(qubits: cudaq.qview, angles: list[float]):
        """Angle-encode a tensor block: RY(2*atan(x_i)) per qubit."""
        for q in range(qubits.size()):
            ry(2.0 * angles[q], qubits[q])

    @cudaq.kernel
    def tensor_roll_rotate(qubits: cudaq.qview, angles: list[float]):
        """Per-qubit RY rotations (the 'roll' of the register)."""
        for q in range(qubits.size()):
            ry(angles[q], qubits[q])

    @cudaq.kernel
    def tensor_roll_entangle(qubits: cudaq.qview):
        """Nearest-neighbour CNOT ladder."""
        for q in range(qubits.size() - 1):
            x.ctrl(qubits[q], qubits[q + 1])

    @cudaq.kernel
    def tensor_roll_measure(qubits: cudaq.qview):
        """Measure the full register in the Z basis."""
        mz(qubits)

    @cudaq.kernel
    def tensor_roll_recursive(qubits: cudaq.qview, angles: list[float],
                              depth: int):
        """Recursive kernel: each level rotates + entangles a shrinking
        active qubit subset, then measures it. Implemented iteratively over
        levels (CUDA-Q kernels unroll statically); depth selects how many
        levels execute."""
        n = qubits.size()
        for level in range(depth):
            # active subset shrinks with depth: n, n-1, ...
            for q in range(n):
                if q < n - level:
                    ry(angles[q], qubits[q])
            for q in range(n - 1):
                if q < n - 1 - level:
                    x.ctrl(qubits[q], qubits[q + 1])
            if level < depth - 1:
                # mid-circuit measurement of the outer qubit, then reset
                # for the next recursion level
                mz(qubits[n - 1 - level])
                reset(qubits[n - 1 - level])
        mz(qubits)

    @cudaq.kernel
    def tensor_roll_reconstruct(counts_register: cudaq.qview):
        """Measurement kernel whose counts are classically post-processed
        into amplitude estimates (see tensor_roll/qsim.py for the estimator).
        Reconstruction itself is classical by construction."""
        mz(counts_register)


def run_smoke(n_qubits: int = 4, shots: int = 1024) -> dict:
    """Execute the kernel family on the qpp-cpu simulator target and return
    a real invocation summary. Requires the CUDA-Q toolchain."""
    if not _HAS_CUDAQ:
        raise RuntimeError(
            "CUDA-Q toolchain not installed (pip install cudaq). "
            "This machine cannot execute these kernels; use the "
            "qsim-classical path (tensor-roll qkernel) instead.")
    import math
    cudaq.set_target("qpp-cpu")
    angles = [2.0 * math.atan(0.1 * (i + 1)) for i in range(n_qubits)]

    @cudaq.kernel
    def _smoke_entry(n_qubits: int, angles: list[float], depth: int):
        qubits = cudaq.qvector(n_qubits)
        tensor_roll_encode(qubits, angles)
        tensor_roll_recursive(qubits, angles, depth)

    counts = cudaq.sample(_smoke_entry, n_qubits, angles, 2,
                          shots_count=shots)
    return {"counts": {k: int(v) for k, v in counts.items()},
            "shots": shots, "target": cudaq.get_target().name}


if __name__ == "__main__":
    try:
        print(run_smoke())
    except RuntimeError as e:
        print(f"unavailable: {e}")