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
Physical QPUs currently do not support analog functionals built on AnalogEvolution;
for now, analog hardware can run only pulse experiments from 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.variables import BinaryVariable, LEQ
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 definidos en qilisdk.experiments.experiment_functional. Estos objetos funcionales reflejan las interfaces descritas en el capítulo Functionals y devuelven tipos de resultado enriquecidos.
import numpy as np
from qilisdk.speqtrum import DeviceType, SpeQtrum
from qilisdk.experiments import RabiExperiment, T1Experiment
client = SpeQtrum()
device = client.list_devices(
where=lambda d: d.type in (DeviceType.QPU_ANALOG, DeviceType.QPU_DIGITAL)
)[0].code
# Rabi experiment: sweep drive durations
rabi = RabiExperiment(qubit=0, drive_duration_values=np.linspace(0, 200, 21))
rabi_handle = client.submit(rabi, device=device)
rabi_response = client.wait_for_job(rabi_handle, timeout=600)
rabi_result = rabi_response.get_results()
# T1 relaxation experiment: sweep wait durations
t1 = T1Experiment(qubit=0, wait_duration_values=np.linspace(0, 400, 41))
t1_handle = client.submit(t1, device=device)
t1_response = client.wait_for_job(t1_handle, timeout=600)
t1_result = t1_response.get_results()
Los objetos RabiExperimentResult y T1ExperimentResult resultantes pueden usarse directamente.