SpeQtrum

El paquete speqtrum proporciona un cliente sincrónico opcional para la nube SpeQtrum de Qilimanjaro. A través de la clase SpeQtrum se puede autenticar, inspeccionar dispositivos y trabajos, y enviar experimentos digitales, analógicos o de pulso para su ejecución remota.

Instalación

El soporte de SpeQtrum se distribuye como un grupo de dependencias opcional. Instálelo junto con QiliSDK con:

pip install qilisdk[speqtrum]

Autenticación

La API utiliza tokens OAuth de corta duración que se almacenan en caché en el llavero del sistema. Llame a SpeQtrum.login una vez y las credenciales se reutilizarán en sesiones posteriores.

from qilisdk.speqtrum import SpeQtrum

# Credentials can be provided explicitly…
logged_in = SpeQtrum.login(username="alice", apikey="MY_SECRET_KEY")

# …or read from the environment (QILISDK_SPEQTRUM_USERNAME / QILISDK_SPEQTRUM_APIKEY)
logged_in = SpeQtrum.login()

if not logged_in:
    raise RuntimeError("Authentication failed")

# Remove cached credentials when they are no longer needed
SpeQtrum.logout()

Construcción del cliente

Una vez almacenadas las credenciales, instancie SpeQtrum para comenzar a emitir solicitudes. La construcción falla con un RuntimeError si no existen credenciales en caché.

from qilisdk.speqtrum import SpeQtrum

client = SpeQtrum()

Catálogo de dispositivos

Los dispositivos se representan mediante modelos Device que contienen el código del dispositivo, el número de cúbits, el tipo de hardware y el estado. Use SpeQtrum.list_devices para enumerarlos. Un predicado where opcional permite el filtrado en el lado del cliente.

from qilisdk.speqtrum import SpeQtrum, DeviceStatus

client = SpeQtrum()
for device in client.list_devices(where=lambda d: d.status == DeviceStatus.ONLINE):
    print(f"{device.code}: {device.name} ({device.type}) – {device.nqubits} qubits")

Trabajos remotos

SpeQtrum.list_jobs devuelve registros ligeros JobInfo. El predicado where funciona de la misma manera que con los dispositivos.

from qilisdk.speqtrum import SpeQtrum
from qilisdk.speqtrum.speqtrum_models import JobStatus

client = SpeQtrum()
running = client.list_jobs(where=lambda job: job.status == JobStatus.RUNNING)
for job in running:
    print(f"{job.id}: {job.status.value} on {job.device_id}")

Para inspeccionar los metadatos completos de un trabajo (carga útil, resultado, registros, errores decodificados) llame a SpeQtrum.get_job. Los campos binarios se devuelven como cadenas decodificadas u objetos ExecuteResult estructurados.

Cuando se espera a un JobHandle, el objeto devuelto es un TypedJobDetail que expone un auxiliar con tipado fuerte get_results().

job_handle = client.submit(sampling, device=device)
final_job = client.wait_for_job(job_handle)
result = final_job.get_results()  # -> FunctionalResult

Aún puede llamar a get_job() con un identificador entero simple. En ese caso se devuelve un objeto regular JobDetail y puede inspeccionar manualmente los campos *.result individuales cuando sea necesario.

Espera de finalización

Use SpeQtrum.wait_for_job para sondear hasta que un trabajo alcance un estado terminal (completed, error o cancelled). Pasar un JobHandle produce un TypedJobDetail con acceso tipado a los resultados, mientras que los identificadores enteros simples continúan devolviendo un JobDetail simple. El auxiliar lanza un TimeoutError si el tiempo de espera opcional expira primero.

Envío de funcionales

SpeQtrum acepta los mismos funcionales primitivos utilizados por los backends locales. El método SpeQtrum.submit inspecciona el tipo de funcional y serializa la carga útil correcta. Debe proporcionar un argumento device con el código de dispositivo obtenido de list_devices().

from qilisdk.digital import Circuit, H, CNOT
from qilisdk.functionals import DigitalPropagation
from qilisdk.readout import Readout
from qilisdk.speqtrum import SpeQtrum

circuit = Circuit(2)
circuit.add(H(0))
circuit.add(CNOT(0, 1))
functional = DigitalPropagation(circuit)

client = SpeQtrum()
device = client.list_devices()[0].code
job_handle = client.submit(functional, readout=Readout().with_sampling(nshots=1_000), device=device)
print("Submitted job:", job_handle.id)

final_job = client.wait_for_job(job_handle, timeout=600)
result = final_job.get_results()
print("Most frequent outcome:", result.probabilities)

Advertencia

Las QPU físicas actualmente no soportan funcionales analógicos basados en AnalogEvolution; por ahora, el hardware analógico solo puede ejecutar experimentos de pulso de experiments.

Programas variacionales

La optimización híbrida se gestiona mediante el mismo funcional VariationalProgram utilizado con los backends locales. Serialice el programa variacional completamente configurado (ansatz, optimizador, función de coste) y envíelo como cualquier otro funcional.

from qilisdk.core.model import Model, ObjectiveSense
from qilisdk.core.comparison import LEQ
from qilisdk.core.variables import BinaryVariable
from qilisdk.cost_functions import ModelCostFunction
from qilisdk.digital import CNOT, HardwareEfficientAnsatz, U2
from qilisdk.functionals import DigitalPropagation
from qilisdk.functionals.variational_program import VariationalProgram
from qilisdk.readout import Readout
from qilisdk.optimizers.scipy_optimizer import SciPyOptimizer
from qilisdk.speqtrum import SpeQtrum

# Build a small cost model
vars = [BinaryVariable(f"x{i}") for i in range(3)]
model = Model("toy")
model.set_objective(sum(vars), sense=ObjectiveSense.MAXIMIZE)
model.add_constraint("budget", LEQ(vars[0] + vars[1], 1))

ansatz = HardwareEfficientAnsatz(
    nqubits=3,
    layers=2,
    one_qubit_gate=U2,
    two_qubit_gate=CNOT,
    connectivity="linear",
    structure="grouped",
)
functional = DigitalPropagation(ansatz)
optimizer = SciPyOptimizer(method="Powell")
vprog = VariationalProgram(functional=functional, optimizer=optimizer, cost_function=ModelCostFunction(model))

client = SpeQtrum()
device = client.list_devices()[0].code
job_handle = client.submit(vprog, readout=Readout().with_sampling(nshots=1024), device=device)

Experimentos de pulso

El cliente SpeQtrum también admite experimentos de estilo de calibración. Un experimento es cualquier subclase de ExperimentFunctional; los experimentos concretos (Rabi, T1, T2, espectroscopía, …) se proporcionan de forma privada al usuario de SpeQtrum, junto con la subclase de ExperimentResult correspondiente. Los experimentos se envían sin readout, ya que la adquisición la define el propio experimento.

from qilisdk.speqtrum import DeviceType, SpeQtrum

client = SpeQtrum()
device = client.list_devices(
    where=lambda d: d.type in (DeviceType.QPU_ANALOG, DeviceType.QPU_DIGITAL)
)[0].code

# `experiment` is an ExperimentFunctional subclass provided to the user.
handle = client.submit(experiment, device=device)
response = client.wait_for_job(handle, timeout=600)
result = response.get_results()

El ExperimentResult resultante contiene los datos de barrido adquiridos (s21, s21_modulus, s21_db, s21_phase) y su método plot representa directamente el barrido 1D o 2D.