Skip to content

Shots Backend

CuStateVecShotsBackend()

A pytket Backend using GeneralState to obtain shots.

Constructs a new cuStateVec backend object.

Source code in pytket/extensions/custatevec/backends/custatevec_backend.py
def __init__(self) -> None:
    """Constructs a new cuStateVec backend object."""
    super().__init__()

backend_info property

Returns information on the backend.

process_circuit(circuit, n_shots=None, valid_check=True, **kwargs)

Submits circuits to the backend for running.

Source code in pytket/extensions/custatevec/backends/custatevec_backend.py
def process_circuit(
    self,
    circuit: Circuit,
    n_shots: int | None = None,
    valid_check: bool = True,
    **kwargs: KwargTypes,
) -> ResultHandle:
    """Submits circuits to the backend for running."""
    return self.process_circuits(
        [circuit],
        n_shots=n_shots,
        valid_check=valid_check,
        **kwargs,
    )[0]

process_circuits(circuits, n_shots=None, valid_check=True, **kwargs)

Submits circuits to the backend for running and returns result handles.

Parameters:

Name Type Description Default
circuits Sequence[Circuit]

List of circuits to be submitted.

required
n_shots int | Sequence[int] | None

Number of shots for shot-based calculation.

None
valid_check bool

Whether to check for circuit correctness.

True

Returns:

Type Description
list[ResultHandle]

List of result handles for the submitted circuits.

Source code in pytket/extensions/custatevec/backends/custatevec_backend.py
def process_circuits(  # noqa: D417
    self,
    circuits: Sequence[Circuit],
    n_shots: int | Sequence[int] | None = None,
    valid_check: bool = True,
    **kwargs: KwargTypes,
) -> list[ResultHandle]:
    """Submits circuits to the backend for running and returns result handles.

    Args:
        circuits: List of circuits to be submitted.
        n_shots: Number of shots for shot-based calculation.
        valid_check: Whether to check for circuit correctness.

    Returns:
        List of result handles for the submitted circuits.
    """
    if n_shots is None:
        raise ValueError("n_shots must be specified for shot-based simulation.")

    all_shots = [n_shots] * len(circuits) if isinstance(n_shots, int) else n_shots

    if valid_check:
        self._check_all_circuits(circuits, nomeasure_warn=False)

    handle_list = []
    for circuit, circ_shots in zip(circuits, all_shots, strict=False):
        with CuStateVecHandle() as libhandle:
            sv = initial_statevector(
                handle=libhandle,
                n_qubits=circuit.n_qubits,
                sv_type="zero",
                dtype=cudaDataType.CUDA_C_64F,
            )
            run_circuit(libhandle, circuit, sv)

            # IMPORTANT: _qubit_idx_map matches cuStateVec's little-endian convention
            # (qubit 0 = least significant) with pytket's big-endian (qubit 0 = most significant).
            # Now all operations by the cuStateVec library will be in the correct order.
            _qubit_idx_map: dict[Qubit, int] = {q: i for i, q in enumerate(sorted(circuit.qubits, reverse=True))}
            # Get relabeled qubit indices that will be measured
            measured_qubits = [_qubit_idx_map[x] for x in circuit.qubit_readout]
            # IMPORTANT: After relabling with _qubit_idx_map, cuStateVec.sampler_sample function still
            # requires its list of measured qubits to be in the LSB-to-MSB order.
            # This reversal adapts our MSB-first list to the LSB-first format cuStateVec requires.
            measured_qubits.reverse()

            sampler_descriptor, size_t = cusv.sampler_create(  # type: ignore[no-untyped-call]
                handle=libhandle.handle,
                sv=sv.array.data.ptr,
                sv_data_type=cudaDataType.CUDA_C_64F,
                n_index_bits=sv.n_qubits,
                n_max_shots=circ_shots,
            )

            bit_strings_int64 = np.empty((circ_shots, 1), dtype=np.int64)  # needs to be int64

            # Generate random numbers for sampling
            seed = kwargs.get("seed")
            rng = np.random.default_rng(seed)
            randnums = np.atleast_1d(rng.random(circ_shots, dtype=np.float64)).tolist()

            cusv.sampler_preprocess(  # type: ignore[no-untyped-call]
                handle=libhandle.handle,
                sampler=sampler_descriptor,
                extra_workspace=0,
                extra_workspace_size_in_bytes=0,
            )

            cusv.sampler_sample(  # type: ignore[no-untyped-call]
                handle=libhandle.handle,
                sampler=sampler_descriptor,
                bit_strings=bit_strings_int64.ctypes.data,
                bit_ordering=measured_qubits,
                bit_string_len=len(measured_qubits),
                randnums=randnums,
                n_shots=n_shots,
                output=SamplerOutput.RANDNUM_ORDER,
            )

            cusv.sampler_destroy(sampler_descriptor)  # type: ignore[no-untyped-call]

        handle = ResultHandle(str(uuid4()))

        # Reformat bit_strings from list of 64-bit signed integer (memory-efficient
        # way for custatevec to save many shots) to list of binaries for OutcomeArray
        bit_strings_binary = [format(s, f"0{len(measured_qubits)}b") for s in bit_strings_int64.flatten().tolist()]
        bit_strings_binary = [tuple(map(int, binary)) for binary in bit_strings_binary]  # type: ignore[misc]

        # In order to be able to use the BackendResult functionality,
        # we only pass the array of the statevector to BackendResult
        self._cache[handle] = {
            "result": BackendResult(
                state=cp.asnumpy(sv.array),
                shots=OutcomeArray.from_readouts(bit_strings_binary),
            ),
        }
        handle_list.append(handle)
    return handle_list