Interfaz de proveedores
qiskit.providers
Este módulo contiene las clases que se utilizan para crear proveedores externos para Qiskit. Un proveedor es cualquier entidad que preste un servicio externo a Qiskit. El ejemplo típico de esto es un proveedor de backend que proporciona Backend objetos que pueden utilizarse para ejecutar QuantumCircuit objetos. Este módulo contiene las clases abstractas que se utilizan para definir la interfaz entre un proveedor y Qiskit.
Soporte de la versión
Cada clase abstracta de la interfaz de proveedores está versionada individualmente. Cuando necesitemos realizar un cambio en una interfaz, se creará una nueva clase abstracta para definir la nueva interfaz. No se garantiza que estos cambios de interfaz sean compatibles con versiones anteriores.
Cambios en la versión
Cada versión menor de qiskit puede incrementar la versión de cualquier interfaz backend un único número de versión. Será un agregado de todos los cambios de interfaz para esa versión en esa interfaz.
Política de soporte de versiones
Para que los proveedores tengan tiempo de adaptarse a los cambios en esta interfaz, Qiskit admitirá varias versiones de cada clase a la vez. Dada la naturaleza de una versión por lanzamiento, la política de depreciación de versiones es un poco más conservadora que la política de depreciación estándar. Qiskit dará soporte a una versión de la interfaz del proveedor durante un mínimo de 3 versiones menores o la primera versión después de 6 meses desde la versión que introdujo una versión, lo que sea más largo, antes de una posible depreciación. A partir de ese momento, se aplicará la política de obsoletos estándar a esa versión de la interfaz. De este modo, los proveedores y usuarios dispondrán de tiempo suficiente para adaptarse a los posibles cambios de última hora en la interfaz. Digamos, por ejemplo, que en 0.19.0 se presenta BackendV2 y 3 meses después de 0.19.0 se publican 0.20.0, 0.21.0 y 0.22.0, y 7 meses después de 0.19.0 se publica 0.23.0. En 0.23.0 podemos dejar obsoleto BackendV2, y es necesario que siga siendo compatible y no se puede eliminar hasta que se complete la política de obsoletos.
Vale la pena señalar que la política de soporte de versiones de Qiskit no significa que los propios proveedores tendrán la misma historia de soporte, pueden (y posiblemente deberían) actualizar a versiones más recientes tan pronto como puedan, la ventana de soporte es sólo para las versiones soportadas de Qiskit. Parte de esta larga ventana antes de la eliminación es dar a los proveedores tiempo suficiente para hacer su propia eliminación de un posible cambio que afecte al usuario final en una parte de la interfaz que esté de cara al usuario antes de actualizar su versión. Por ejemplo, supongamos que cambiamos la firma a Backend.run() en BackendV34 de forma incompatible con versiones anteriores. Antes de que Aer pudiera actualizar su clase AerSimulator para basarse en la versión 34, tendrían que dejar obsoleta la antigua firma antes de pasar a la nueva. No está garantizado que el cambio de Aer se produzca al mismo tiempo que el de Qiskit, por lo que debemos asegurarnos de que haya tiempo suficiente para que Aer complete su ciclo de obsoleto antes de eliminar la versión 33 (es decir, que la versión 34 sea obligatoria/la versión mínima).
Clases abstractas
Programa de fondo
Columna « 1 » | Columna « 2 » |
|---|---|
Backend() | Tipo base común para todas las clases abstractas versionadas de Backend. |
BackendV2( [proveedor, nombre, descripción,...] ) | Clase abstracta para backends |
QubitProperties( [t1, t2, frecuencia] ) | Representación de un objeto QubitProperties . |
Opciones
Columna « 1 » | Columna « 2 » |
|---|---|
Options(**kwargs) | Objeto base de opciones |
Trabajo
Columna « 1 » | Columna « 2 » |
|---|---|
Job() | Tipo base común para todas las clases abstractas versionadas de Job. |
JobV1(backend, job_id, *kwargs) | Clase para gestionar trabajos |
Estado del trabajo
Columna « 1 » | Columna « 2 » |
|---|---|
JobStatus(*valores) | Clase para el tipo enumerado de estado del trabajo. |
Excepciones
QiskitBackendNotFoundError
exception qiskit.providers.QiskitBackendNotFoundError(*message)
Bases: QiskitError
Clase base para los errores que se producen al buscar un backend.
Configura el mensaje de error.
JobError
exception qiskit.providers.JobError(*message)
Bases: QiskitError
Clase base para los errores generados por Jobs.
Configura el mensaje de error.
JobTimeoutError
exception qiskit.providers.JobTimeoutError(*message)
Bases: JobError
Clase base para los errores de tiempo de espera generados por los trabajos.
Configura el mensaje de error.
Escribir un nuevo backend
Si tienes un dispositivo cuántico o un simulador que te gustaría integrar con Qiskit, tendrás que escribir un backend. Un proveedor es un conjunto de backends y proporcionará a Qiskit un método para obtener los objetos disponibles BackendV2 . El BackendV2 objeto proporciona tanto información que describe un backend como su funcionamiento, transpiler de modo que los circuitos puedan compilarse en un formato optimizado y ejecutarse en el backend. Además, proporciona el run() método que permite ejecutar los QuantumCircuit objetos. Esto permite a los usuarios y a otras API de Qiskit obtener resultados de la ejecución de circuitos en dispositivos de forma estandarizada, independientemente de cómo se haya implementado el backend. A grandes rasgos, los pasos básicos para escribir un proveedor son los siguientes:
Implementar una clase
Providerque gestione el acceso al backend(s).Implementa una
BackendV2subclase y surun()método.
- Añade cualquier puerta personalizada para la base del backend a la instancia de sesión
EquivalenceLibrary.Implementa una
JobV1subclase que se encargue de interactuar con un trabajo en ejecución.
Para ver un ejemplo sencillo de proveedor, consulte qiskit-aqt-provider
Proveedor
Una clase proveedora tiene un único propósito: obtener objetos backend que permitan ejecutar circuitos en un dispositivo o simulador. La expectativa es que cualquier credencial requerida y/o autenticación sea manejada en la inicialización de un objeto proveedor. El objeto proveedor proporcionará entonces una lista de backends, y métodos para filtrar y adquirir backends (utilizando las credenciales proporcionadas si es necesario). Una clase de proveedor de ejemplo tiene el siguiente aspecto:
from qiskit.providers.providerutils import filter_backends
from .backend import MyBackend
class MyProvider:
def __init__(self, token=None):
super().__init__()
self.token = token
self.backends = [MyBackend(provider=self)]
def backends(self, name=None, **kwargs):
if name:
backends = [
backend for backend in backends if backend.name() == name]
return filter_backends(backends, filters=filters, **kwargs)Asegúrate de que la clase contenga toda la información necesaria para la autenticación (si procede) y de que el método del backend se ajuste a la interfaz requerida. El resto depende de cada proveedor en cuanto a cómo llevarlo a cabo.
Programa de fondo
Las clases de backend constituyen el núcleo del proveedor. Estas clases son las que proporcionan la interfaz entre Qiskit y el hardware o el simulador que ejecutará los circuitos. Esto incluye proporcionar la información necesaria para describir un backend al compilador, de modo que este pueda integrar y optimizar cualquier circuito para dicho backend. Hay cuatro elementos obligatorios en cada objeto de backend: una target propiedad para definir el modelo del backend para el compilador, una max_circuits propiedad para definir un límite en el número de circuitos que el backend puede ejecutar en un único trabajo por lotes (si no hay límite None , se puede utilizar «»), un run() método para aceptar envíos de trabajos y un _default_options método para definir las opciones configurables por el usuario y sus valores por defecto. Por ejemplo, un ejemplo básico que funcione sería algo así como:
from qiskit.providers import BackendV2 as Backend
from qiskit.transpiler import Target
from qiskit.providers import Options
from qiskit.circuit import Parameter, Measure
from qiskit.circuit.library import PhaseGate, SXGate, UGate, CXGate, IGate
class Mybackend(Backend):
def __init__(self):
super().__init__()
# Create Target
self._target = Target("Target for My Backend")
# Instead of None for this and below instructions you can define
# a qiskit.transpiler.InstructionProperties object to define properties
# for an instruction.
lam = Parameter("λ")
p_props = {(qubit,): None for qubit in range(5)}
self._target.add_instruction(PhaseGate(lam), p_props)
sx_props = {(qubit,): None for qubit in range(5)}
self._target.add_instruction(SXGate(), sx_props)
phi = Parameter("φ")
theta = Parameter("ϴ")
u_props = {(qubit,): None for qubit in range(5)}
self._target.add_instruction(UGate(theta, phi, lam), u_props)
cx_props = {edge: None for edge in [(0, 1), (1, 2), (2, 3), (3, 4)]}
self._target.add_instruction(CXGate(), cx_props)
meas_props = {(qubit,): None for qubit in range(5)}
self._target.add_instruction(Measure(), meas_props)
id_props = {(qubit,): None for qubit in range(5)}
self._target.add_instruction(IGate(), id_props)
# Set option validators
self.options.set_validator("shots", (1, 4096))
self.options.set_validator("memory", bool)
@property
def target(self):
return self._target
@property
def max_circuits(self):
return 1024
@classmethod
def _default_options(cls):
return Options(shots=1024, memory=False)
def run(self, circuits, **kwargs):
# serialize circuits submit to backend and create a job
for kwarg in kwargs:
if not hasattr(self.options, kwarg):
warnings.warn(
"Option %s is not used by this backend" % kwarg,
UserWarning, stacklevel=2)
options = {
'shots': kwargs.get('shots', self.options.shots),
'memory': kwargs.get('memory', self.options.memory),
}
job_json = convert_to_wire_format(circuit, options)
job_handle = submit_to_backend(job_json)
return MyJob(self.job_handle, job_json, circuit)Interfaz del transpilador del backend
La parte fundamental del Backend objeto es la forma en que se describe a sí mismo ante el compilador. Esto se gestiona mediante la Target clase que define un modelo de backend para el transpilador. Un objeto de backend deberá devolver un Target objeto del target atributo que la transpile() función utilizará como modelo de un destino de backend para la compilación.
Puertas personalizadas
-
Si tu backend no utiliza puertas lógicas de la biblioteca de circuitos de Qiskit (
qiskit.circuit.library), puedes integrar la compatibilidad con ellas en tu proveedor. El método básico para hacerlo consiste, en primer lugar, en definir unaGatesubclase para cada puerta personalizada del conjunto base. Por ejemplo:import numpy as np from qiskit.circuit import Gate from qiskit.circuit import QuantumCircuit class SYGate(Gate): def __init__(self, label=None): super().__init__("sy", 1, [], label=label) def _define(self): qc = QuantumCircuit(1) qc.ry(np.pi / 2, 0) self.definition = qcLo más importante es asegurarse de que el atributo name de su puerta personalizada (el primer parámetro en
super().__init__()en la definición de__init__) no entra en conflicto con el nombre de ninguna otra puerta. El atributo name es lo que se utiliza para identificar la puerta en el conjunto de bases para el transpilador. Si hay un conflicto, el transpilador no sabrá qué puerta utilizar. -
Añade la puerta de acceso personalizada al destino de tu backend. Esto se puede hacer con el
Target.add_instruction()método. Tendrás que añadir una instancia deSYGatey sus parámetros al destino para que el transpilador sepa que existe. Por ejemplo, supongamos que esto forma parte de laBackendV2implementación de tu backend:from qiskit.transpiler import InstructionProperties sy_props = { (0,): InstructionProperties(duration=2.3e-6, error=0.0002), (1,): InstructionProperties(duration=2.1e-6, error=0.0001), (2,): InstructionProperties(duration=2.5e-6, error=0.0003), (3,): InstructionProperties(duration=2.2e-6, error=0.0004), } self.target.add_instruction(SYGate(), sy_props)Las claves de
sy_propsdefinen los qubits en los que se puede utilizar el backendSYGate, y los valores definen las propiedades deSYGateen ese qubit. Para las puertas multiqubit, las claves de tupla contienen todas las combinaciones de qubits con las que funciona la puerta (el orden es significativo, es decir.(0, 1)es diferente de(1, 0)). -
Una vez definidas las puertas personalizadas que se van a utilizar para el conjunto de bases del backend, es necesario añadir reglas de equivalencia a la biblioteca de equivalencias estándar para que la
transpile()función ytranspilerel módulo puedan convertir un circuito arbitrario utilizando el conjunto de bases personalizado. Esto se puede hacer definiendo circuitos equivalentes, en términos de la puerta personalizada, para las puertas estándar. Normalmente, si puedes realizar la conversión a partir de unaCXGate(si tu base no incluye una puerta estándar de 2 qubits) y algunas puertas de rotación de un solo qubit de uso común, como laHGateyUGatela, eso debería ser suficiente para que el transpilador traduzca cualquier circuito a las puertas de la base personalizada. Sin embargo, cuantas más reglas de equivalencia se definan a partir de las puertas estándar y tu base, más eficiente será la conversión de un circuito arbitrario a la base de destino (aunque no siempre es así, y el margen de beneficio va disminuyendo).Por ejemplo, si quisieras añadir algunas reglas para la configuración
U2GatepersonalizadaSYGateanterior, podríamos definir yHGate:from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary from qiskit.circuit.library import HGate from qiskit.circuit.library import ZGate from qiskit.circuit.library import RZGate from qiskit.circuit.library import U2Gate # H => Z SY q = qiskit.QuantumRegister(1, "q") def_sy_h = qiskit.QuantumCircuit(q) def_sy_h.append(ZGate(), [q[0]], []) def_sy_h.append(SYGate(), [q[0]], []) SessionEquivalenceLibrary.add_equivalence( HGate(), def_sy_h) # u2 => Z SY Z phi = qiskit.circuit.Parameter('phi') lam = qiskit.circuit.Parameter('lambda') q = qiskit.QuantumRegister(1, "q") def_sy_u2 = qiskit.QuantumCircuit(q) def_sy_u2.append(RZGate(lam), [q[0]], []) def_sy_u2.append(SYGate(), [q[0]], []) def_sy_u2.append(RZGate(phi), [q[0]], []) SessionEquivalenceLibrary.add_equivalence( U2Gate(phi, lam), def_sy_u2)Te interesará que esto se ejecute al importar, de modo que se ejecute en cuanto se importe el paquete del proveedor. De este modo se garantizará que, cada vez que se ejecute la
BasisTranslatorpasada con las puertas personalizadas, se definan las reglas de equivalencia.También conviene señalar que, dependiendo de la base que utilices, es posible que algunas etapas de optimización del transpilador, como
Optimize1qGatesDecomposition, no puedan funcionar con tu base personalizada. En nuestroSYGateejemplo, no será posible simplificar lasOptimize1qGatesDecompositionsecuencias de puertas de un solo qubit en la base SY. Esto se debe a que laOneQubitEulerDecomposerclase no sabe cómo trabajar en la base SY. Para resolver esto, habría que añadir laSYGateclase a Qiskit yOneQubitEulerDecomposeractualizarla para que admita la descomposición en elSYGate. A largo plazo, probablemente esa sea la mejor dirección a seguir para las puertas personalizadas, y aportar las definiciones y el soporte al transpilador garantizará que Qiskit siga ofreciendo un buen soporte en el futuro.
Pasadas personalizadas del transpilador
El transpilador permite que los backends proporcionen implementaciones personalizadas de las etapas del transpilador para facilitar optimizaciones específicas del hardware y transformaciones de circuitos. Actualmente hay dos etapas compatibles, get_translation_stage_plugin() y get_scheduling_stage_plugin() , que permiten a un backend especificar nombres de complementos de cadena que se utilizarán como etapas predeterminadas de traducción y programación, respectivamente. Estos puntos de conexión de una BackendV2 clase se pueden utilizar si tu backend tiene requisitos de compilación que no cumple el backend oTarget la interfaz actuales. Te rogamos que consideres también la posibilidad de enviar una incidencia en GitHub en la que describas tu caso de uso, ya que existe interés en mejorar estas interfaces para poder describir más arquitecturas de hardware con mayor detalle.
Para aprovechar estos puntos de enganche, solo tienes que añadir los métodos a tu BackendV2 implementación y hacer que devuelvan un nombre de plugin en forma de cadena. Por ejemplo:
class Mybackend(BackendV2):
def get_scheduling_stage_plugin(self):
return "SpecialDD"
def get_translation_stage_plugin(self):
return "BasisTranslatorWithCustom1qOptimization"Este fragmento de implementación del backend hará que la función transpile() utilice por defecto el plugin SpecialDD para la etapa de programación y el plugin BasisTranslatorWithCustom1qOptimization para la etapa de traducción cuando el destino sea Mybackend. Tenga en cuenta que los usuarios pueden anular estas opciones seleccionando explícitamente un nombre de plugin diferente. Para que esta interfaz funcione, los plugins de etapa del transpilador deben estar implementados para el nombre de plugin devuelto. Puede consultar la qiskit.transpiler.preset_passmanagers.plugin para más detalles sobre cómo implementar plugins. La expectativa típica es que si su backend requiere pases personalizados como parte de una etapa de compilación, el paquete del proveedor incluirá los plugins de la etapa de transpilación que utilizan esos pases. Sin embargo, esto no es necesario y se puede utilizar cualquier método válido (de un método incorporado o de un plugin externo).
De este modo, si estos dos pasos de compilación son necesarios para ejecutar o proporcionar una salida eficiente en Mybackend , el transpilador podrá realizar estos pasos personalizados sin ninguna intervención manual del usuario.
Variables en tiempo real
El transpilador tratará automáticamente las variables clásicas tipadas en tiempo real (véase qiskit.circuit.classical) y tratará la instrucción Store como una "directiva" incorporada, similar a la instrucción Barrier. No es necesario ningún tratamiento especial por parte de los backends para permitirlo.
Si tu backend no es capaz de gestionar variables y almacenamiento clásicos, te recomendamos que lo indiques en tu documentación e incluyas una comprobación en tu run() método (véase el método « Backend.run ») para rechazar de forma inmediata los circuitos que los contengan. Puedes comprobar QuantumCircuit.num_vars si hay variables en el nivel superior. Si aceptas operaciones de flujo de control, es posible que tengas que buscar de forma recursiva en el interior blocks de cada una de ellas las variables locales del ámbito con QuantumCircuit.num_declared_vars.
Por ejemplo, una función para comprobar la presencia de cualquier almacén manual, o almacenes manuales en la memoria:
from qiskit.circuit import Store, ControlFlowOp, QuantumCircuit
def has_realtime_logic(circuit: QuantumCircuit) -> bool:
if circuit.num_vars:
return True
for instruction in circuit.data:
if isinstance(instruction.operation, Store):
return True
elif isinstance(instruction.operation, ControlFlowOp):
for block in instruction.operation.blocks:
if has_realtime_logic(block):
return True
return FalseLímites angulares en Gates
Si tu backend tiene restricciones sobre los valores permitidos de los parámetros para cualquier puerta del objetivo, puedes modelarlo estableciendo límites de ángulo en el Target. Al añadir la instrucción con el add_instruction() , puedes utilizar el angle_bounds argumento de palabra clave, que admite una lista de tuplas que representan los límites superior e inferior del parámetro de una puerta lógica.
Por ejemplo, este fragmento de código, en lugar del ejemplo en el que se añade PhaseGate en el ejemplo anterior:
lam = Parameter("λ")
p_props = {(qubit,): None for qubit in range(5)}
self._target.add_instruction(PhaseGate(lam), p_props, angle_bounds=[(0, math.pi)])establecerá los límites de PhaseGate entre 0 y , ambos inclusive. Esto modela la restricción angular en los Target valores angulares del lam parámetro en PhaseGate. La fase del WrapAngles transpilador se utiliza para transformar cualquier PhaseGate elemento que se encuentre fuera de los límites de ángulo especificados. Tendrás que escribir una función que tome como parámetros los valores de los ángulos de la compuerta y devuelva un DAGCircuit. Por ejemplo:
from qiskit.transpiler.passes import WrapAngles
def fold_phase(angles: List[float], qubits: List[int]) -> DAGCircuit:
angle = angles[0]
if angle > 0:
number_of_gates = angle / math.pi
else:
number_of_gates = (6.28 - angle) / math.pi
dag = DAGCircuit()
dag.add_qubits([Qubit()])
for _ in range(int(number_of_gates)):
dag.apply_operation_back(PhaseGate(math.pi), [dag.qubits[0]])
return dag
WrapAngles.DEFAULT_REGISTRY.add_wrapper("phase", fold_phase)Esta función transformará las puertas fuera de límites en una que respete los límites angulares del objetivo y las demás restricciones del objetivo (aunque no especialmente bien).
Backend.run Método
Es de vital importancia el run() método que se utiliza para enviar realmente los circuitos a un dispositivo o a un simulador. El método «run» se encarga de enviar los circuitos al backend para su ejecución y de devolver un Job objeto. Dependiendo del tipo de backend, esto suele implicar la serialización del objeto de circuito al formato de API utilizado por dicho backend. Dado que las necesidades de serialización del backend pueden variar (y, en el caso de los simuladores locales, es posible que ni siquiera sea necesaria la serialización), se espera que el método del run backend se encargue de esta conversión.
Un ejemplo de método de ejecución sería algo como
def run(self, circuits, **kwargs):
for kwarg in kwargs:
if not hasattr(self.options, kwarg):
warnings.warn(
"Option %s is not used by this backend" % kwarg,
UserWarning, stacklevel=2)
options = {
'shots': kwargs.get('shots', self.options.shots),
'memory': kwargs.get('memory', self.options.memory),
}
job_json = convert_to_wire_format(circuit, options)
job_handle = submit_to_backend(job_json)
return MyJob(self.job_handle, job_json, circuit)Opciones del backend
A menudo hay varias opciones de backend que controlan cómo se ejecuta un circuito. Un ejemplo típico de esto es algo así como el número de shots , que indica cuántas veces se debe ejecutar el circuito. Las opciones disponibles para un backend se definen mediante un Options objeto. Este objeto se crea inicialmente mediante el _default_options método de una clase Backend. Las opciones predeterminadas devuelven un objeto inicializado Options con todos los valores predeterminados para todas las opciones que admite un backend. Por ejemplo, si el backend solo admite shots el _default_options método, quedaría así:
@classmethod
def _default_options(cls):
return Options(shots=1024)También puedes configurar validadores en un Options objeto para establecer límites y validar los valores proporcionados por el usuario en función de lo que sea aceptable para tu backend. Por ejemplo, si la "shots" opción definida anteriormente se puede establecer en cualquier valor entre 1 y 4096, puedes configurar el validador en el objeto de opciones de tu backend de la siguiente manera:
self.options.set_validator("shots", (1, 4096))Puedes consultar la set_validator() documentación para ver una lista completa de las opciones de validación.
Trabajo
El resultado del método run es un JobV1 objeto. Se espera que cada proveedor implemente una subclase de trabajo personalizada que defina el comportamiento del proveedor. Existen dos tipos de tareas, dependiendo del método de ejecución del backend: sincrónicas o asincrónicas. Por defecto, las tareas se consideran asíncronas y se espera que representen un identificador de la ejecución asíncrona de los circuitos enviados con Backend.run(). Un objeto de tarea asíncrona permite a los usuarios consultar el estado de la ejecución, cancelar una tarea en curso y esperar hasta que finalice la ejecución. El result es el principal método al que acceden los usuarios; este permanecerá bloqueado hasta que finalice la ejecución y, a continuación, devolverá un Result objeto con los resultados del trabajo.
En algunos backends (principalmente simuladores locales), la ejecución de circuitos es una operación síncrona y no es necesario devolver un identificador a un trabajo que se esté ejecutando en otro lugar. En el caso de las tareas de sincronización, se espera que el run método del backend quede bloqueado hasta que se genere un Result objeto y que la tarea de sincronización devuelva dicho objeto interno Result .
Una clase de trabajo de ejemplo para un backend basado en API asíncrona sería algo así:
from qiskit.providers import JobV1 as Job
from qiskit.providers import JobError
from qiskit.providers import JobTimeoutError
from qiskit.providers.jobstatus import JobStatus
from qiskit.result import Result
class MyJob(Job):
def __init__(self, backend, job_id, job_json, circuits):
super().__init__(backend, job_id)
self._backend = backend
self.job_json = job_json
self.circuits = circuits
def _wait_for_result(self, timeout=None, wait=5):
start_time = time.time()
result = None
while True:
elapsed = time.time() - start_time
if timeout and elapsed >= timeout:
raise JobTimeoutError('Timed out waiting for result')
result = get_job_status(self._job_id)
if result['status'] == 'complete':
break
if result['status'] == 'error':
raise JobError('Job error')
time.sleep(wait)
return result
def result(self, timeout=None, wait=5):
result = self._wait_for_result(timeout, wait)
results = [{'success': True, 'shots': len(result['counts']),
'data': result['counts']}]
return Result.from_dict({
'results': results,
'backend_name': self._backend.configuration().backend_name,
'backend_version': self._backend.configuration().backend_version,
'job_id': self._job_id,
'success': True,
})
def status(self):
result = get_job_status(self._job_id)
if result['status'] == 'running':
status = JobStatus.RUNNING
elif result['status'] == 'complete':
status = JobStatus.DONE
else:
status = JobStatus.ERROR
return status
def submit(self):
raise NotImplementedErrory para un trabajo de sincronización:
class MySyncJob(Job):
_async = False
def __init__(self, backend, job_id, result):
super().__init__(backend, job_id)
self._result = result
def submit(self):
return
def result(self):
return self._result
def status(self):
return JobStatus.DONEPrimitivos
Aunque no forma parte directamente de la interfaz del proveedor, el módulo qiskit.primitives está estrechamente vinculado a los proveedores. En concreto, las interfaces primitivas, como BaseSampler y BaseEstimator, están diseñadas para permitir que las implementaciones de los proveedores ofrezcan implementaciones personalizadas optimizadas para los backends de los proveedores. Esto puede incluir personalizaciones como transformaciones de circuitos, pre y postprocesamiento adicional, procesamiento por lotes, almacenamiento en caché, mitigación de errores, etc. El concepto del módulo qiskit.primitives es permitir explícitamente esto, ya que los objetos primitivos son abstracciones de nivel superior para producir resultados procesados de nivel superior (como distribuciones de probabilidad y valores de expectativa) que abstraen la mecánica de obtener el mejor resultado de manera eficiente, para concentrarse en aplicaciones de nivel superior que utilizan estos resultados.
Por ejemplo, si sus backends estuvieran bien adaptados para aprovechar la mitigación de mediciones de mthree para mejorar la calidad de los resultados, podría implementar una implementación de Sampler específica del proveedor que aprovechara la clase M3Mitigation internamente para ejecutar los circuitos y devolver cuasi-probabilidades directamente de mthree en el resultado. Esto permitiría a los algoritmos obtener los mejores resultados con la mitigación aplicada directamente desde sus backends. Puede consultar la documentación en qiskit.primitives sobre cómo escribir implementaciones personalizadas. Además, las implementaciones incorporadas: Sampler, Estimator, BackendSampler, y BackendEstimator pueden servir como referencias/modelos sobre cómo implementarlas también.