Skip to main content
IBM Quantum Platform

Interfaz de proveedores

qiskit.providers

Este módulo contiene las clases utilizadas para construir proveedores externos para Qiskit. Un proveedor es cualquier cosa que proporcione un servicio externo a Qiskit. El ejemplo típico de esto es un proveedor Backend que proporciona Backend que pueden utilizarse para ejecutar objetos 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

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

Options(**kwargs)Objeto base de opciones

Trabajo

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

JobStatus(*valores)Clase para el tipo enumerado de estado del trabajo.

Excepciones

QiskitBackendNotFoundError

exception qiskit.providers.QiskitBackendNotFoundError(*message)

GitHub

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)

GitHub

Bases: QiskitError

Clase base para los errores generados por Jobs.

Configura el mensaje de error.

JobTimeoutError

exception qiskit.providers.JobTimeoutError(*message)

GitHub

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 una colección de backends y proporcionará a Qiskit un método para obtener los objetos disponibles BackendV2 disponibles. El objeto BackendV2 proporciona tanto la información que describe un backend como su funcionamiento para el transpiler para que los circuitos puedan ser compilados a algo que esté optimizado y pueda ejecutarse en el backend. También proporciona el método run() que puede ejecutar los objetos 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 estándar, independientemente de cómo se implemente el backend. A grandes rasgos, los pasos básicos para redactar un proveedor son los siguientes:

  • Implementar una clase Provider que gestione el acceso al backend(s).

  • Implementar una BackendV2 subclase y su método run() método.

    • Añadir cualquier puerta personalizada para la base del backend a la sesión EquivalenceLibrary de la sesión.
  • Implementar una JobV1 que se encarga 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 son el núcleo del proveedor. Estas clases son las que proporcionan la interfaz entre Qiskit y el hardware o simulador que ejecutará los circuitos. Esto incluye proporcionar la información necesaria para describir un backend al compilador para que pueda incrustar y optimizar cualquier circuito para el backend. Hay 4 cosas necesarias en cada objeto backend: una propiedad target para definir el modelo del backend para el compilador, una propiedad max_circuits 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 se puede usar None ), un método run() para aceptar envíos de trabajos, y un método _default_options para definir las opciones configurables por el usuario y sus valores por defecto. Por ejemplo, un ejemplo mínimo de trabajo sería algo así:

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 pieza clave del Backend es cómo se describe a sí mismo al compilador. Esto se gestiona con la clase Target que define un modelo de backend para el transpilador. Un objeto backend deberá devolver un Target del atributo target que la función transpile() utilizará como modelo de un objetivo de backend para la compilación.

Puertas personalizadas

  1. Si tu backend no utiliza puertas en la librería de circuitos Qiskit (qiskit.circuit.library) puedes integrar soporte para ello en tu proveedor. El método básico para hacerlo es definir primero una subclase Gate subclase para cada puerta personalizada del conjunto de bases. 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 = qc

    Lo 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.

  2. Añade la puerta personalizada al objetivo de tu backend. Esto puede hacerse con el método Target.add_instruction() método. Tendrás que añadir una instancia de SYGate y sus parámetros al destino para que el transpilador sepa que existe. Por ejemplo, suponiendo que esto es parte de su BackendV2 para su 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_props definen los qubits en los que se puede utilizar el backend SYGate , y los valores definen las propiedades de SYGate en 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)).

  3. Una vez definidas las puertas personalizadas que se utilizarán para el conjunto de bases del backend, es necesario añadir reglas de equivalencia a la biblioteca de equivalencias estándar para que la función transpile() y el módulo transpiler pueda convertir un circuito arbitrario utilizando el conjunto de bases personalizado. Esto puede hacerse definiendo circuitos equivalentes, en términos de la puerta personalizada, para puertas estándar. Típicamente si puedes convertir desde una CXGate (si tu base no incluye una puerta estándar de 2 qubits) y algunas puertas de rotación de un qubit de uso común como la HGate y UGate eso debería ser suficiente para que el transpilador traduzca cualquier circuito a las puertas de la base personalizada. Pero, cuantas más reglas de equivalencia se definan de las puertas estándar a su base, más eficiente será la traducción de un circuito arbitrario a la base objetivo (aunque no siempre, y hay un margen de rendimiento decreciente).

    Por ejemplo, si tuviéramos que añadir algunas reglas para el anterior SYGate personalizado, podríamos definir los parámetros U2Gate y HGate:

    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)

    Usted querrá que esto se ejecute en la importación para que tan pronto como el paquete del proveedor sea importado se ejecute. Esto garantizará que cada vez que se ejecute el BasisTranslator con las puertas personalizadas se definan las reglas de equivalencia.

    También hay que tener en cuenta que, dependiendo de la base que utilices, algunos pases de optimización del transpilador, como por ejemplo Optimize1qGatesDecompositionpueden no funcionar con tu base personalizada. Para nuestro ejemplo SYGate Optimize1qGatesDecomposition no podrá simplificar las ejecuciones de puertas de un solo qubit en la base SY. Esto se debe a que la clase OneQubitEulerDecomposer no sabe trabajar en la base SY. Para solucionarlo, habría que añadir la clase SYGate a Qiskit y actualizar OneQubitEulerDecomposer para que admita la descomposición en SYGate. A largo plazo, es probable que sea una mejor dirección para las puertas base personalizadas y contribuir con las definiciones y el apoyo en el transpilador asegurará que siga siendo bien apoyado por Qiskit en el futuro.

Pasadas personalizadas del transpilador

El transpilador admite la posibilidad de que los backends proporcionen implementaciones de etapas de transpilador personalizadas para facilitar optimizaciones específicas de hardware y transformaciones de circuitos. Actualmente hay dos etapas soportadas, get_translation_stage_plugin() y get_scheduling_stage_plugin() que permiten a un backend especificar nombres de plugin de cadena para ser utilizados como la traducción por defecto y las etapas de programación, respectivamente. Estos puntos de enganche en una clase BackendV2 se pueden utilizar si su backend tiene requisitos de compilación que no cumple la interfaz backend/Target actual. Por favor, considere también la posibilidad de enviar una incidencia a Github describiendo su caso de uso, ya que hay interés en mejorar estas interfaces para poder describir más arquitecturas de hardware con mayor profundidad.

Para aprovechar estos puntos de enganche sólo tienes que añadir los métodos a tu implementación de BackendV2 y hacer que devuelvan una cadena con el nombre del plugin. 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 manejar variables clásicas y almacenamiento, te recomendamos que lo comentes en tu documentación, e insertes una comprobación en tu método run() (véase Backend.run Method ) para rechazar de forma inmediata los circuitos que los contengan. Puede examinar QuantumCircuit.num_vars la presencia de variables en el nivel superior. Si acepta operaciones de flujo de control, es posible que tenga que buscar recursivamente en el interno blocks de cada una de ellas en busca de variables locales de á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 False

Límites angulares en Gates

Si su backend tiene restricciones en los valores de los parámetros permitidos para cualquier puerta en el objetivo, puede modelar esto con límites angulares en el parámetro Target. Cuando añada la instrucción con el add_instruction() puede utilizar el argumento de la palabra clave angle_bounds que toma una lista de tuplas para el límite superior e inferior del parámetro de una puerta.

Por ejemplo, este fragmento de código en lugar del ejemplo que añade el icono 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)])

fijará los límites de PhaseGate entre 0 y π\pi (ambos inclusive). Esto modela la restricción angular en el Target en los valores angulares para el parámetro lam en PhaseGate. El WrapAngles transpiler pass se utiliza para transformar cualquier PhaseGate fuera de los límites de ángulo especificados. Tendrás que escribir una función que reciba los valores angulares de la puerta y devuelva un valor 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 circuitos a un dispositivo o simulador. El método run se encarga de enviar los circuitos al backend para que se ejecuten y devolver un Job objeto. Dependiendo del tipo de backend, esto suele implicar la serialización del objeto de circuito al formato API utilizado por el 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 run del 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 determinan 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 Options inicializado 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 puede establecer validadores en un objeto Options para proporcionar límites y validación en los valores proporcionados por el usuario basados en lo que es aceptable para su backend. Por ejemplo, si la opción "shots" definida anteriormente se puede establecer en cualquier valor entre 1 y 4096, puede establecer el validador en el objeto de opciones para su backend con:

self.options.set_validator("shots", (1, 4096))

puede consultar la documentación set_validator() para obtener una lista completa de las opciones de validación.

Trabajo

La salida del método run es un objeto JobV1 objeto. Se espera que cada proveedor implemente una subclase de trabajo personalizada que defina el comportamiento del proveedor. Existen 2 tipos de trabajos dependiendo del método de ejecución del backend, ya sea sync o async. Por defecto, los trabajos se consideran asíncronos y se espera que representen un asidero para la ejecución asíncrona de los circuitos enviados con Backend.run(). Un objeto de trabajo asíncrono proporciona a los usuarios la capacidad de consultar el estado de la ejecución, cancelar un trabajo en ejecución y bloquear hasta que la ejecución finalice. El método result es el método principal de cara al usuario que se bloqueará hasta que la ejecución se complete y entonces devolverá un objeto Result con los resultados del trabajo.

Para algunos backends (principalmente simuladores locales) la ejecución de circuitos es una operación sincrónica y no hay necesidad de devolver un handle a un trabajo en ejecución en otro lugar. Para las tareas de sincronización se espera que el método run en el backend se bloquee hasta que se genere un objeto Result y el trabajo de sincronización regrese con ese objeto interno Result interno.

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 NotImplementedError

y 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.DONE

Primitivos

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.

¿Le ha resultado útil esta página?
Informe de un error, de una errata o solicite contenido en GitHub.