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)
Bases: QiskitError
Classe base para erros gerados durante a procura de um backend.
Defina a mensagem de erro.
JobError
exception qiskit.providers.JobError(*message)
Bases: QiskitError
Classe base para erros gerados por Jobs.
Defina a mensagem de erro.
JobTimeoutError
exception qiskit.providers.JobTimeoutError(*message)
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
Providerque lida com o acesso ao(s) backend(s).Implemente uma
BackendV2subclasse e seurun()método.
- Adicione quaisquer portas personalizadas para a base do backend à instância da sessão
EquivalenceLibrary.Implemente uma
JobV1subclasse 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
-
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 umaGatesubclasse 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 = qcO 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. -
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 deSYGatee seus parâmetros ao destino para que o transpilador saiba que ela existe. Por exemplo, supondo que isso faça parte da suaBackendV2implementaçã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_propsdefinem os qubits em que o backendSYGatepode ser usado, e os valores definem as propriedades deSYGatenesse 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)). -
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 etranspilero 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 umCXGate(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 aHGateeUGatea, 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çãoSYGateacima, poderíamos definir oU2Gatee 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
BasisTranslatorexecuçã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 nossoSYGateexemplo, não será possível simplificar sequências de portas de umOptimize1qGatesDecompositionúnico qubit na base SY. Isso ocorre porque aOneQubitEulerDecomposerclasse não sabe como operar na base SY. Para resolver isso, aSYGateclasse precisaria ser adicionada ao Qiskit eOneQubitEulerDecomposeratualizada para suportar a decomposição para oSYGate. 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 FalseLimites 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 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 NotImplementedErrore 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.DONEPrimitivas
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.