Skip to main content
IBM Quantum Platform

Interface dos fornecedores

qiskit.providers

Este módulo contém as classes utilizadas para criar provedores externos para o Qiskit. Um provedor é qualquer entidade que forneça um serviço externo ao Qiskit. Um exemplo típico disso é um provedor de backend que fornece Backend objetos que podem ser usados para executar QuantumCircuit objetos. Este módulo contém as classes abstratas utilizadas para definir a interface entre um provedor e o Qiskit.


Suporte a Versões

Cada classe abstrata da interface de provedores é versionada individualmente. Quando precisarmos fazer uma alteração em uma interface, uma nova classe abstrata será criada para definir a nova interface. Não há garantia de que essas alterações na interface sejam compatíveis com versões anteriores.

Alterações na versão

Cada lançamento de versão secundária do qiskit pode incrementar a versão de qualquer interface de backend em um único número de versão. Será um agregado de todas as alterações de interface para essa versão nessa interface.

Política de suporte de versão

Para permitir que os provedores tenham tempo de se ajustar às mudanças nessa interface, o Qiskit oferecerá suporte a várias versões de cada classe ao mesmo tempo. Dada a natureza de uma versão por lançamento, a política de depreciação de versão é um pouco mais conservadora do que a política de depreciação padrão. O Qiskit oferecerá suporte a uma versão de interface de provedor por um mínimo de 3 versões secundárias ou a primeira versão após 6 meses do lançamento que introduziu uma versão, o que for mais longo, antes de uma possível depreciação. Depois disso, a política de depreciação padrão será aplicada a essa versão da interface. Isso dará aos provedores e usuários tempo suficiente para se adaptarem às possíveis mudanças significativas na interface. Por exemplo, digamos que em 0.19.0 BackendV2 é apresentado e, 3 meses após o lançamento de 0.19.0, lançamos 0.20.0, 0.21.0 e 0.22.0 e, 7 meses após 0.19.0, lançamos 0.23.0. Em 0.23.0, podemos descontinuar o BackendV2, e ele ainda precisa ter suporte e não pode ser removido até que a política de descontinuação seja concluída.

Vale ressaltar que a política de suporte à versão do Qiskit não significa que os próprios provedores terão a mesma história de suporte. Eles podem (e, sem dúvida, devem) atualizar para versões mais recentes assim que puderem, a janela de suporte é apenas para as versões suportadas pelo Qiskit. Parte dessa longa janela antes da descontinuação é para dar aos provedores tempo suficiente para fazer sua própria descontinuação de uma possível alteração que afete o usuário final em uma parte da interface voltada para o usuário antes de atualizar sua versão. Por exemplo, digamos que alteramos a assinatura para Backend.run() em BackendV34 de uma forma incompatível com as versões anteriores. Antes que a Aer pudesse atualizar sua classe AerSimulator para se basear na versão 34, ela precisaria descontinuar a assinatura antiga antes de fazer a mudança. Não há garantia de que a mudança para o Aer seja feita em sincronia com o Qiskit, portanto, precisamos garantir que haja tempo suficiente para que o Aer conclua seu ciclo de depreciação antes de remover a versão 33 (ou seja, tornar a versão 34 obrigatória/ mínima).


Classes abstratas

Back-end

Coluna “ 1 ”
Coluna “ 2 ”
Backend()Tipo comum básico para todas as classes abstratas de backend com versão.
BackendV2( [provedor, nome, descrição,...] )Classe abstrata para backends
QubitProperties( [t1, t2, frequência] )Uma representação de um objeto QubitProperties .

Opções

Coluna “ 1 ”
Coluna “ 2 ”
Options(**kwargs)Objeto de opções de base

Tarefa

Coluna “ 1 ”
Coluna “ 2 ”
Job()Tipo comum básico para todas as classes abstratas do Job com controle de versão.
JobV1(backend, job_id, *kwargs)Classe para lidar com trabalhos

Status da tarefa

Coluna “ 1 ”
Coluna “ 2 ”
JobStatus(*valores)Classe para o tipo enumerado de status do trabalho.

Exceções

QiskitBackendNotFoundError

exception qiskit.providers.QiskitBackendNotFoundError(*message)

GitHub

Bases: QiskitError

Classe base para erros gerados durante a procura de um backend.

Defina a mensagem de erro.

JobError

exception qiskit.providers.JobError(*message)

GitHub

Bases: QiskitError

Classe base para erros gerados por Jobs.

Defina a mensagem de erro.

JobTimeoutError

exception qiskit.providers.JobTimeoutError(*message)

GitHub

Bases: JobError

Classe base para erros de tempo limite gerados por trabalhos.

Defina a mensagem de erro.


Escrevendo um novo backend

Se você tiver um dispositivo quântico ou um simulador que gostaria de integrar ao Qiskit, precisará desenvolver um backend. Um provedor é um conjunto de back-ends e fornecerá ao Qiskit um método para obter os objetos disponíveis BackendV2 . O BackendV2 objeto fornece tanto informações que descrevem um backend quanto seu funcionamento, transpiler de modo que os circuitos possam ser compilados em um código otimizado e executável nesse backend. Ele também fornece o run() método que permite executar os QuantumCircuit objetos. Isso permite que os usuários e outras APIs do Qiskit obtenham resultados da execução de circuitos em dispositivos de maneira padronizada, independentemente de como o backend esteja implementado. Em linhas gerais, as etapas básicas para escrever um provedor são:

  • Implemente uma classe Provider que lida com o acesso ao(s) backend(s).

  • Implemente uma BackendV2 subclasse e seu run() método.

    • Adicione quaisquer portas personalizadas para a base do backend à instância da sessão EquivalenceLibrary .
  • Implemente uma JobV1 subclasse que lide com a interação com um trabalho em execução.

Para obter um exemplo simples de um provedor, consulte o qiskit-aqt-provider

Provedor

Uma classe de provedor tem uma única finalidade: obter objetos de back-end que permitem a execução de circuitos em um dispositivo ou simulador. A expectativa é que todas as credenciais e/ou autenticação necessárias sejam tratadas na inicialização de um objeto de provedor. O objeto do provedor fornecerá uma lista de back-ends e métodos para filtrar e adquirir back-ends (usando as credenciais fornecidas, se necessário). Um exemplo de classe de provedor tem a seguinte aparência:

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)

Certifique-se de que todas as informações necessárias para a autenticação (se for o caso) estejam presentes na classe e que o método do backend corresponda à interface exigida. O resto depende de cada provedor específico quanto à forma de implementação.

Back-end

As classes de backend são o núcleo do provedor. Essas classes são as que fornecem a interface entre o Qiskit e o hardware ou simulador que executará os circuitos. Isso inclui fornecer as informações necessárias para descrever um backend ao compilador, de modo que ele possa incorporar e otimizar qualquer circuito para esse backend. Existem quatro elementos obrigatórios em todo objeto de backend: uma target propriedade para definir o modelo do backend para o compilador, uma max_circuits propriedade para definir um limite para o número de circuitos que o backend pode executar em um único trabalho em lote (se não houver limite None , pode-se usar), um run() método para aceitar envios de trabalhos e um _default_options método para definir as opções configuráveis pelo usuário e seus valores padrão. Por exemplo, um exemplo básico que funcione seria algo 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)

Interface do transpiler do backend

O elemento-chave do Backend objeto é a forma como ele se descreve para o compilador. Isso é feito por meio da Target classe que define um modelo de backend para o transpilador. Um objeto de backend precisará retornar um Target objeto a partir do target atributo, que a transpile() função utilizará como seu modelo de um alvo de backend para compilação.

Portões personalizados

  1. Se o seu backend não utiliza portas da biblioteca de circuitos do Qiskit (qiskit.circuit.library), você pode integrar o suporte a elas em seu provedor. O método básico para fazer isso consiste, em primeiro lugar, em definir uma Gate subclasse para cada porta personalizada no conjunto de base. Por exemplo:

    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

    O importante é garantir que, para qualquer porta personalizada na base do seu backend, o atributo name da porta personalizada (o primeiro parâmetro em super().__init__() na definição __init__ acima) não entre em conflito com o nome de nenhuma outra porta. O atributo name é usado para identificar a porta no conjunto de base para o transpilador. Se houver um conflito, o transpilador não saberá qual porta usar.

  2. Adicione o gate personalizado ao destino do seu backend. Isso pode ser feito com o Target.add_instruction() método. Você precisará adicionar uma instância de SYGate e seus parâmetros ao destino para que o transpilador saiba que ela existe. Por exemplo, supondo que isso faça parte da sua BackendV2 implementação do 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)

    As chaves em sy_props definem os qubits em que o backend SYGate pode ser usado, e os valores definem as propriedades de SYGate nesse qubit. Para portas multiqubit, as chaves de tuplas contêm todas as combinações de qubit nas quais a porta funciona (a ordem é significativa, ou seja (0, 1) é diferente de (1, 0)).

  3. Depois de definir os portões personalizados a serem usados para o conjunto de bases do backend, é necessário adicionar regras de equivalência à biblioteca de equivalências padrão para que a transpile() função e transpiler o módulo possam converter um circuito arbitrário utilizando o conjunto de bases personalizado. Isso pode ser feito definindo circuitos equivalentes, em termos da porta personalizada, para as portas padrão. Normalmente, se você conseguir converter a partir de um CXGate (caso sua base não inclua uma porta padrão de 2 qubits) e de algumas portas de rotação de qubit único comumente utilizadas, como a HGate e UGate a, isso deve ser suficiente para que o transpiler traduza qualquer circuito para as portas da base personalizada. No entanto, quanto mais regras de equivalência forem definidas a partir das portas padrão para a sua base, mais eficiente será a conversão de um circuito arbitrário para a base-alvo (embora isso não seja sempre verdade, e haja uma margem de retorno decrescente).

    HGatePor exemplo, se você fosse adicionar algumas regras para a personalização SYGate acima, poderíamos definir o U2Gate e o :

    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)

    É recomendável que isso seja executado na importação, para que seja executado assim que o pacote do provedor for importado. Isso garantirá que, sempre que a BasisTranslator execução for realizada com os portões personalizados, as regras de equivalência estejam definidas.

    Também vale a pena observar que, dependendo da base que você estiver usando, algumas etapas de otimização no transpiler, como Optimize1qGatesDecomposition, podem não funcionar com sua base personalizada. No nosso SYGate exemplo, não será possível simplificar sequências de portas de um Optimize1qGatesDecomposition único qubit na base SY. Isso ocorre porque a OneQubitEulerDecomposer classe não sabe como operar na base SY. Para resolver isso, a SYGate classe precisaria ser adicionada ao Qiskit e OneQubitEulerDecomposer atualizada para suportar a decomposição para o SYGate. A longo prazo, essa provavelmente é a melhor direção a seguir para portas personalizadas, e a inclusão das definições e do suporte no transpiler garantirá que elas continuem a ser bem suportadas pelo Qiskit no futuro.

Passagens personalizadas do transpiler

O transpiler permite que os backends forneçam implementações personalizadas das etapas do transpiler para facilitar otimizações específicas de hardware e transformações de circuitos. Atualmente, há duas etapas suportadas, get_translation_stage_plugin() e get_scheduling_stage_plugin() que permitem que um backend especifique nomes de plug-ins de string para serem usados como as etapas padrão de tradução e agendamento, respectivamente. Esses pontos de conexão em uma BackendV2 classe podem ser usados caso seu backend tenha requisitos de compilação que não sejam atendidos pelo backend/interfaceTarget atual. Por favor, considere também enviar uma solicitação no GitHub descrevendo seu caso de uso, pois há interesse em aprimorar essas interfaces para que seja possível descrever mais arquiteturas de hardware com maior profundidade.

Para aproveitar esses pontos de integração, basta adicionar os métodos à sua BackendV2 implementação e fazer com que eles retornem o nome do plugin como uma string. Por exemplo:

class Mybackend(BackendV2):

    def get_scheduling_stage_plugin(self):
        return "SpecialDD"

    def get_translation_stage_plugin(self):
        return "BasisTranslatorWithCustom1qOptimization"

Esse trecho de uma implementação de backend agora fará com que a função transpile() use o plug-in SpecialDD para a etapa de agendamento e o plug-in BasisTranslatorWithCustom1qOptimization para a etapa de tradução por padrão quando o destino estiver definido como Mybackend. Observe que os usuários podem substituir essas opções selecionando explicitamente um nome de plug-in diferente. Para que essa interface funcione, os plug-ins de estágio do transpilador devem ser implementados para o nome do plug-in retornado. Você pode consultar a qiskit.transpiler.preset_passmanagers.plugin para obter detalhes sobre como implementar plug-ins. A expectativa típica é que, se o seu backend exigir passes personalizados como parte de um estágio de compilação, o pacote do provedor incluirá os plug-ins do estágio do transpilador que usam esses passes. No entanto, isso não é obrigatório e qualquer método válido (de um método interno ou plug-in externo) pode ser usado.

Dessa forma, se essas duas etapas de compilação forem necessárias para a execução ou para fornecer uma saída eficiente em Mybackend , o transpilador poderá executar essas etapas personalizadas sem nenhuma entrada manual do usuário.

Variáveis em tempo real

O transpilador tratará automaticamente as variáveis clássicas digitadas em tempo real (consulte qiskit.circuit.classical) e tratará a instrução Store como uma "diretriz" incorporada, semelhante à instrução Barrier. Não é necessário nenhum tratamento especial dos backends para permitir isso.

Se o seu backend não for capaz de lidar com variáveis e armazenamento clássicos, recomendamos que você comente sobre isso na sua documentação e insira uma verificação no seu código. run() método (consulte o método Backend.run ) para rejeitar imediatamente os circuitos que os contêm. Você pode verificar QuantumCircuit.num_vars se há variáveis no nível superior. Se você aceitar operações de fluxo de controle, talvez seja necessário pesquisar recursivamente o escopo interno blocks de cada for em busca de variáveis locais desse escopo com QuantumCircuit.num_declared_vars.

Por exemplo, uma função para verificar a presença de locais de armazenamento manual ou armazenamentos manuais na memória:

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

Limites angulares em Gates

Se o seu backend tiver restrições quanto aos valores permitidos para os parâmetros de qualquer porta no alvo, você pode modelar isso com limites de ângulo no Target. Ao adicionar a instrução com o add_instruction() , você pode usar o angle_bounds argumento de palavra-chave, que recebe uma lista de tuplas representando os limites superior e inferior do parâmetro de uma porta lógica.

Por exemplo, este trecho de código, em vez do exemplo que adiciona o PhaseGate no exemplo acima:

lam = Parameter("λ")
p_props = {(qubit,): None for qubit in range(5)}
self._target.add_instruction(PhaseGate(lam), p_props, angle_bounds=[(0, math.pi)])

definirá os limites de PhaseGate entre 0 e π\pi o (inclusive). Isso modela a restrição angular nos valores Target de ângulo para o lam parâmetro em PhaseGate. A etapa do WrapAngles transpiler é usada para transformar qualquer valor PhaseGate fora dos limites de ângulo especificados. Você precisará escrever uma função que receba os valores dos ângulos do portão e retorne um DAGCircuit. Por exemplo:

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)

Essa função transformará os portões fora dos limites em um que respeite os limites de ângulo no alvo e as outras restrições do alvo (embora não muito bem).

Backend.run Método

De fundamental importância é o run() método utilizado para, de fato, enviar circuitos a um dispositivo ou simulador. O método run é responsável por enviar os circuitos ao backend para execução e por retornar um Job objeto. Dependendo do tipo de backend, isso geralmente envolve a serialização do objeto de circuito no formato de API utilizado pelo backend. Como as necessidades de serialização do backend podem variar (e, no caso de simuladores locais, a serialização pode nem mesmo ser necessária), espera-se que o método do run backend lide com essa conversão.

Um exemplo de método de execução seria 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)

Opções de backend

Muitas vezes, há várias opções de backend que determinam como um circuito é executado. Um exemplo típico disso é algo como o número de shots , que representa quantas vezes o circuito deve ser executado. As opções disponíveis para um backend são definidas por meio de um Options objeto. Esse objeto é criado inicialmente pelo método _default_options de uma classe Backend. As opções padrão retornam um objeto inicializado Options com todos os valores padrão para todas as opções suportadas por um backend. Por exemplo, se o backend suportar shots apenas o _default_options método, ele ficaria assim:

@classmethod
def _default_options(cls):
    return Options(shots=1024)

Você também pode definir validadores em um Options objeto para estabelecer limites e validar os valores fornecidos pelo usuário, com base no que é aceitável para o seu backend. Por exemplo, se a "shots" opção definida acima puder ser configurada com qualquer valor entre 1 e 4096, você pode definir o validador no objeto de opções do seu backend da seguinte forma:

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

Você pode consultar a set_validator() documentação para obter uma lista completa das opções de validação.

Tarefa

O resultado do método run é um JobV1 objeto. Espera-se que cada provedor implemente uma subclasse de tarefa personalizada que defina o comportamento do provedor. Existem dois tipos de tarefas, dependendo do método de execução do backend: síncrono ou assíncrono. Por padrão, as tarefas são consideradas assíncronas, e espera-se que isso represente um identificador para a execução assíncrona dos circuitos enviados com Backend.run(). Um objeto de tarefa assíncrona permite que os usuários consultem o status da execução, cancelem uma tarefa em andamento e aguardem até que a execução seja concluída. O result é o principal método voltado para o usuário, que permanecerá bloqueado até que a execução seja concluída e, em seguida, retornará um Result objeto com os resultados da tarefa.

Para alguns backends (principalmente simuladores locais), a execução de circuitos é uma operação síncrona e não há necessidade de retornar um identificador para um trabalho em execução em outro lugar. Para tarefas de sincronização, espera-se que o run método no backend permaneça em espera até que um Result objeto seja gerado e que a tarefa de sincronização retorne com esse objeto interno Result .

Um exemplo de classe de trabalho para um backend baseado em API assíncrona seria algo parecido com:

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

e para um trabalho de sincronização:

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

Primitivas

Embora não faça parte diretamente da interface do provedor, o módulo qiskit.primitives é fortemente acoplado aos provedores. Especificamente, as interfaces primitivas, como BaseSampler e BaseEstimator, foram projetadas para permitir que as implementações do provedor forneçam implementações personalizadas otimizadas para os back-ends do provedor. Isso pode incluir personalizações como transformações de circuitos, pré e pós-processamento adicionais, lotes, armazenamento em cache, atenuação de erros etc. O conceito do módulo qiskit.primitives é permitir isso explicitamente, pois os objetos primitivos são abstrações de nível superior para produzir resultados processados de nível superior (como distribuições de probabilidade e valores de expectativa) que abstraem a mecânica de obter o melhor resultado de forma eficiente, para se concentrar em aplicativos de nível superior que usam esses resultados.

Por exemplo, se seus back-ends fossem adequados para aproveitar a mitigação de medição do mthree para melhorar a qualidade dos resultados, você poderia implementar uma implementação Sampler específica do provedor que aproveitasse a classe M3Mitigation internamente para executar os circuitos e retornar quase-probabilidades diretamente do mthree no resultado. Isso permitiria que os algoritmos obtivessem os melhores resultados com a atenuação aplicada diretamente de seus back-ends. Você pode consultar a documentação em qiskit.primitives sobre como escrever implementações personalizadas. Além disso, as implementações incorporadas: Sampler, Estimator, BackendSampler e BackendEstimator podem servir como referências/modelos sobre como implementá-las também.

Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.