Modelo de circuito quântico
qiskit.circuit
O elemento fundamental da computação quântica é o circuito quântico. Essa é uma rotina de computação que pode ser executada, uma tentativa por vez, em uma unidade de processamento quântico (QPU). Um circuito atuará em uma quantidade predefinida de dados quânticos (no Qiskit, só oferecemos suporte direto a qubits) com operações unitárias (portas), medições e redefinições. Além disso, um circuito quântico pode conter operações em dados clássicos, incluindo computações em tempo real e construções de fluxo de controle, que são executadas pelos controladores da QPU.
Talvez você queira pular o material introdutório e ir diretamente para o primeiro passo:
- a visão geral da API de todo o módulo de circuito
- a discussão detalhada sobre como os circuitos são representados
- o núcleo
QuantumCircuitpara saber como criar e consultar circuitos - informações sobre instruções personalizadas de construção
- maneiras de trabalhar com objetos em nível de circuito
- discussão sobre as convenções do Qiskit para circuitos, matrizes e rotulagem de estados
Os circuitos estão em um nível baixo de abstração na criação de programas quânticos. Eles são a construção usada para construir níveis mais altos de abstração, como os primitivos da computação quântica, que acumulam dados de muitas tomadas de execução de circuitos quânticos, juntamente com técnicas avançadas de mitigação de erros e otimizações de medição, em dados clássicos bem tipados e estatísticas de erros.
No Qiskit, os circuitos podem ser definidos em um de dois regimes :
- um circuito abstrato, que é definido em termos de qubits virtuais e operações arbitrárias de alto nível, como algoritmos encapsulados e portas definidas pelo usuário.
- um circuito físico, que é definido em termos de qubits de hardware de um backend específico e contém apenas operações que esse backend suporta nativamente. Você também pode ver esse conceito ser chamado de circuito ISA.
Você converte de um circuito abstrato para um circuito físico usando o pacote de transpilação do Qiskit, cujo ponto de acesso de nível superior é transpile(). Se você definir um circuito, no qual pretende que os índices de qubit se refiram a qubits físicos, poderá usar QuantumCircuit.ensure_physical() para reescrever os metadados do circuito a fim de garantir que o Qiskit reconheça o circuito como um circuito físico, embora, diferentemente da transpilação, isso não imponha que as restrições de base-gates e de acoplamento de hardware sejam respeitadas.
No Qiskit, um circuito quântico é representado pela classe QuantumCircuit classe. Abaixo está um exemplo de um circuito quântico que produz um estado Greenberger-Horne-Zeilinger (GHZ) de três qubits definido como:
from qiskit import QuantumCircuit
# Create a circuit with a register of three qubits
circ = QuantumCircuit(3)
# H gate on qubit 0, putting this qubit in a superposition of |0> + |1>.
circ.h(0)
# A CX (CNOT) gate on control qubit 0 and target qubit 1 generating a Bell state.
circ.cx(0, 1)
# CX (CNOT) gate on control qubit 0 and target qubit 2 resulting in a GHZ state.
circ.cx(0, 2)
# Draw the circuit
circ.draw('mpl')
Conceitos e definições de circuitos
Há muita terminologia especializada em torno dos circuitos quânticos. Grande parte disso é comum na literatura sobre computação quântica, enquanto alguns são mais específicos para pacotes de software quântico e uma pequena quantidade é específica para o Qiskit. Esta é uma lista em ordem alfabética de alguns dos conceitos importantes como referência rápida, mas não entra em detalhes sobre os conceitos fundamentais. Considere a possibilidade de usar a plataforma IBM Quantum Learning se você quiser começar do início.
circuito abstrato
Um circuito definido em termos de operações matemáticas abstratas e qubits virtuais. Normalmente é assim que você pensa sobre algoritmos quânticos; um circuito abstrato pode ser composto de operações unitárias completamente arbitrárias, medições e potencialmente computação clássica em tempo real, sem restrições sobre quais qubits podem interagir uns com os outros.
Você transforma um circuito abstrato em um circuito físico usando o pacote de transpilação do Qiskit.
ancilla qubit
Um qubit extra que é usado para ajudar a implementar operações em outros qubits, mas cujo estado final não é importante para o programa.
circuito
Uma rotina computacional que define uma única execução a ser realizada em uma QPU. Pode ser um circuito abstrato ou um circuito físico.
clbit
Abreviação específica do Qiskit que significa um único bit clássico de dados.
porta
Uma operação unitária em um ou mais qubits.
qubit de hardware
A representação de um único qubit em uma QPU específica. Um qubit de hardware tem algum sistema físico quântico-mecânico de apoio, como circuitos supercondutores; ao contrário de um qubit virtual, ele tem restrições de acoplamento específicas e somente determinadas portas podem ser aplicadas a determinados grupos de qubits de hardware.
O Qiskit não distingue os qubits lógicos de nenhum qubits físico individual ao falar sobre qubits de hardware. Uma QPU pode implementar seus qubits de hardware como qubits lógicos, em que cada qubit de hardware compreende muitos qubits físicos que são controlados e corrigidos por erros de forma opaca para o Qiskit pelos componentes eletrônicos de controle. É mais provável que, para aplicativos de curto prazo, uma QPU exponha diretamente seus qubits físicos como qubits de hardware para o Qiskit raciocinar.
Os qubits físicos e lógicos terão restrições de acoplamento entre si, permitirão apenas determinadas operações quânticas neles e terão preocupações de agendamento entre eles. O Qiskit abstrai essas preocupações no conceito de qubits de hardware. Nos primeiros dias da correção de erros quânticos, determinados back-ends podem permitir que você acesse seus recursos de qubit como qubits lógicos de alto nível ou como qubits físicos de baixo nível por meio do Qiskit.
arquitetura de conjunto de instruções (ISA)
O modelo abstrato de quais operações estão disponíveis em quais conjuntos de qubits de hardware em uma QPU específica. Por exemplo, uma QPU pode permitir operações e em todos os qubits de hardware individuais e operações em determinados pares de qubits de hardware.
qubit lógico
Uma coleção de vários qubits físicos que são controlados em conjunto por uma QPU (da perspectiva do usuário) para aplicar a correção de erros quânticos em tempo real. Um qubit lógico é um tipo de qubit de hardware para o Qiskit.
medida
O ato de extrair um bit clássico de um dado de um único estado de qubit. Essa é uma operação irreversível e geralmente destrói o emaranhamento e a coerência de fase entre o qubit de destino e o restante do sistema.
circuito físico
Um circuito definido em termos de qubits de hardware e somente as operações quânticas disponíveis em um ISA específico da QPU. Os circuitos físicos estão vinculados a uma arquitetura QPU específica e não serão executados em outras arquiteturas incompatíveis. Você também pode ouvir esse termo ser chamado de circuito ISA.
Normalmente, você obtém um circuito físico usando as rotinas de transpilação do Qiskit em um circuito abstrato que você construiu.
qubit físico
Um sistema quântico controlável de dois níveis. Trata-se literalmente de um qubit "físico", como um transmônio ou o estado eletrônico de um íon aprisionado. Uma QPU pode expor isso diretamente como seu qubit de hardware ou combinar vários qubits físicos em um qubit lógico.
unidade de processamento quântico (QPU)
Análogo a uma CPU na computação clássica ou a uma GPU no processamento gráfico, uma QPU é o hardware que executa operações quânticas em dados quânticos. Você sempre pode esperar que uma QPU que usa o modelo de circuito de computação seja capaz de executar algum conjunto de portas e operações de medição. Dependendo da tecnologia específica, eles também podem ser capazes de executar alguns cálculos clássicos em tempo real, como fluxo de controle clássico e cálculos bit a bit em dados clássicos.
Qubit
A unidade básica da informação quântica.
computação clássica em tempo real
Qualquer computação clássica que possa ocorrer durante a execução de uma única tentativa de um circuito, em que os resultados da computação clássica possam afetar a execução posterior do circuito. A quantidade de computação clássica em tempo real disponível com determinadas QPUs variará significativamente, dependendo de muitos fatores, como os componentes eletrônicos de controle e a tecnologia de qubit em uso. Você deve consultar a documentação do fornecedor do hardware para obter mais informações sobre isso.
operação unitária
Uma operação reversível em um estado quântico. Todas as portas quânticas são operações unitárias (por definição).
qubit virtual
Um qubit abstrato e matemático usado para construir um circuito abstrato. Os qubits virtuais são a forma como normalmente se pensa em algoritmos quânticos em um alto nível; presumimos que todas as portas quânticas são válidas em todos os qubits virtuais e que todos os qubits virtuais estão sempre conectados a todos os outros qubits virtuais.
Ao mapear para o hardware, os qubits virtuais devem ser atribuídos aos qubits de hardware. Esse mapeamento não precisa ser um para um. Normalmente, um qubit virtual precisará ser trocado de um qubit de hardware para outro no decorrer da execução de um circuito para satisfazer as restrições de acoplamento da QPU subjacente. Não é estritamente necessário que todos os qubits virtuais usados em um circuito sejam mapeados para um qubit físico em um determinado ponto de um circuito físico; pode ser que um qubit virtual seja medido (colapsando seu estado) e nunca mais seja usado, de modo que um novo qubit virtual possa tomar seu lugar. A avaliação dessas condições para mapear um circuito virtual para um circuito físico é tarefa do pacote de transpilação do Qiskit.
Visão geral da API do qiskit.circuit
Todos os objetos aqui são descritos em mais detalhes e em seu contexto mais amplo nas seções a seguir. Esta seção fornece uma visão geral dos elementos da API documentados aqui.
A classe principal é a QuantumCircuitque tem sua própria página de documentação, incluindo uma seção detalhada sobre a criação de circuitos. Os dados quânticos e os dados clássicos mais simples são representados por "bits" e "registros":
Em um circuito, cada circuito completo CircuitInstruction completo é composto por um Operation (que pode ser um Instruction, a Gateou alguma outra subclasse) e os operandos qubit e clbit. As principais classes básicas aqui são:
-
CircuitInstruction, uma operação e seus operandos -
InstructionSet, um identificador temporário para uma fatia de dados do circuito -
Operationqualquer objeto matemático abstrato ou instrução de hardware-
AnnotatedOperationuma subclasse com modificadores abstratos aplicados
-
A subclasse concreta mais comum da interface mínima e abstrata Operation mínima e abstrata é a interface Instruction. Embora Operation pode incluir objetos matemáticos abstratos, um Instruction é algo que poderia ser executado diretamente no hardware. Este, por sua vez, é subclasse de Gate e ControlledGate que adicionam ainda mais a unitariedade e a semântica controlada:
Instruction, representando uma instrução baseada em hardwareGate, representando uma instrução de hardware que é unitáriaControlledGaterepresentando uma porta com estrutura de controle.
O Qiskit inclui uma grande biblioteca de portas e circuitos padrão, que está documentada em qiskit.circuit.library. Muitos deles são declarados como Python -object singletons. O mecanismo para isso é descrito em detalhes em qiskit.circuit.singletonem que as classes principais são, cada uma, uma forma única da hierarquia de porta de instruções padrão:
Algumas instruções são particularmente especiais, pois afetam o fluxo de controle ou o fluxo de dados do circuito. Os de nível superior são:
-
Barrierpara marcar as partes do circuito que devem ser otimizadas de forma independente -
Delaypara inserir um período de espera em tempo real -
Resetpara redefinir irreversivelmente um qubit para o estado -
Storepara gravar uma expressão clássica em tempo real em um local de armazenamento -
ControlFlowOpque tem subclasses específicas:BreakLoopOppara sair do loop de contenção mais próximoContinueLoopOppara passar imediatamente para a próxima iteração do loop que o contémBoxOpum simples agrupamento de instruçõesForLoopOppara percorrer um intervalo fixo de valoresIfElseOppara entrar condicionalmente em um dos dois subcircuitosSwitchCaseOppara entrar condicionalmente em um dos muitos subcircuitosWhileLoopOppara repetir um subcircuito até que uma condição seja falsificada.
Certas instruções podem ser "anotadas" com metadados, que normalmente devem ser consumidos por uma passagem do compilador localmente ou em um processamento posterior de back-end. Atualmente, isso está limitado a BoxOp. Essas anotações são representadas por subclasses personalizadas de Annotatione há uma discussão mais aprofundada sobre a infraestrutura de suporte em qiskit.circuit.annotation.
Os circuitos podem incluir expressões clássicas que são avaliadas em tempo real, enquanto a QPU está executando uma única tomada do circuito. Eles são documentados principalmente na documentação do módulo do qiskit.circuit.classical. Você pode estar particularmente interessado nas classes básicas (que não são expostas a partir da qiskit.circuit raiz):
Var, um local de armazenamento clássico digitado em um circuitoExpr, uma expressão avaliada em tempo realTypeo tipo clássico de uma expressão.
Além dessa avaliação de expressão em tempo real, que é limitada por representações clássicas de dados em hardware, o Qiskit tem o conceito de parametrização em "tempo de compilação", que é feita em álgebra simbólica abstrata. Normalmente, eles são usados para representar ângulos de porta em algoritmos de alto nível que podem querer executar derivadas numéricas, mas são uma parte mais antiga do Qiskit do que a avaliação em tempo real, portanto, ainda são usados em alguns lugares para fazer parametrização geral. As principais classes relacionadas são:
Parameter, o átomo de expressões em tempo de compilaçãoParameterExpression, um cálculo simbólico dos parâmetrosParameterVectoruma coleção conveniente de muitosParametersParameterVectorElement, uma subclasse deParameterusada porParameterVector
O módulo qiskit.circuit também expõe algumas classes de cálculo que funcionam com circuitos para auxiliar os fluxos de trabalho de compilação. Dentre eles:
EquivalenceLibraryum banco de dados de relações de decomposição entre portas e circuitosSessionEquivalenceLibraryuma instância mutável deEquivalenceLibraryque é usada por padrão pela funçãoBasisTranslator.
Há também utilitários para gerar circuitos aleatórios:
Por fim, o módulo de circuito tem sua própria classe de exceção, para indicar quando as coisas deram errado de maneiras específicas do circuito:
Representação de circuitos no Qiskit
A principal classe voltada para o usuário para representar circuitos é QuantumCircuit. Esse pode ser um circuito abstrato ou um circuito físico. Há muito mais informações sobre a classe QuantumCircuit e os diversos métodos disponíveis na documentação da classe.
Internamente, um QuantumCircuit contém os qubits, bits clássicos, parâmetros de tempo de compilação, variáveis de tempo real e outras informações de rastreamento sobre os dados em que atua e como é parametrizado. Em seguida, ele contém uma sequência de CircuitInstructions, que contém a operação específica (porta, medição, etc.) e seus operandos (os qubits e os bits clássicos).
Bits e registros
Os Qubits e os bits clássicos são representados por um tipo de base Bit compartilhado, que serve apenas como uma "etiqueta de tipo"; as classes não têm nenhum comportamento além de serem objetos imutáveis:
Bit
Qubit
class qiskit.circuit.Qubit(register=None, index=None)
Bases: Bit
Um qubit, que pode ser comparado entre diferentes circuitos.
Clbit
class qiskit.circuit.Clbit(register=None, index=None)
Bases: Bit
Um clbit, que pode ser comparado entre diferentes circuitos.
Qubits e clbits são instanciados por usuários sem argumentos, como em Qubit(). Os bits serão comparados como iguais se forem o mesmo objeto Python ou se ambos tiverem sido criados por um registro com o mesmo nome e tamanho e se referirem ao mesmo índice dentro desse registro. Há também uma tag de tipo especial para qubits "ancilla", mas ela é pouco usada no estado atual do Qiskit:
AncillaQubit
Uma coleção de bits do mesmo tipo pode ser encapsulada em um registro do tipo correspondente. A funcionalidade básica está em uma classe básica que não é instanciada diretamente:
Registrar
class qiskit.circuit.Register
Bases: object
Implemente um registro genérico.
Essa classe não pode ser instanciada diretamente. Sua única finalidade é permitir a verificação genérica de tipos para ClassicalRegister e QuantumRegister.
Cada um dos subtipos de bits definidos tem um registro associado, que tem as mesmas assinaturas de construtor, métodos e propriedades da classe base:
QuantumRegister
class qiskit.circuit.QuantumRegister(size=None, name=None, bits=None)
Bases: Register
Implemente um registro.
ClassicalRegister
class qiskit.circuit.ClassicalRegister(size=None, name=None, bits=None)
Bases: Register
Implemente um registro.
AncillaRegister
class qiskit.circuit.AncillaRegister(size=None, name=None, bits=None)
Bases: QuantumRegister
Implemente um registro ancilla.
Uma maneira comum de instanciar vários bits de uma vez é criar um registro, como em QuantumRegister("my_qreg", 5). Isso tem a vantagem de que você pode dar um nome a essa coleção de bits, que aparecerá durante as visualizações de circuitos (QuantumCircuit.draw()) e nas exportações para linguagens de intercâmbio (consulte qasm2 e qasm3). Também é possível passar um nome e uma lista de bits pré-construídos, mas isso cria um "registro de aliasing", que é muito mal suportado no hardware.
Os circuitos rastreiam os registros, mas os registros em si não transmitem quase nenhuma diferença de comportamento aos circuitos. A única exceção é que ClassicalRegisters podem ser implicitamente convertidos em inteiros sem sinal para uso em comparações condicionais de operações de fluxo de controle.
Os registros e bits clássicos eram a forma original de representar dados clássicos no Qiskit e continuam sendo os mais suportados atualmente. A longo prazo, o modelo de dados está se movendo em direção a uma representação mais completa e fortemente tipada de uma gama de dados clássicos (consulte Computação clássica em tempo real ), mas você ainda usará muito comumente bits clássicos no Qiskit atual.
Contextos de instrução
O tipo escalar da sequência QuantumCircuit.data é o objeto "contexto de instrução", CircuitInstruction. Basicamente, trata-se apenas de uma classe de dados que contém uma representação do que deve ser feito (seu operation) e os dados sobre os quais ele atua (o qubits e clbits).
CircuitInstruction(operação[, qubits, clbits] ) | Uma única instrução em um QuantumCircuité composta pelo endereço operation e por vários operandos. |
Em termos de programação, essa classe é de fato implementada no Rust e é um identificador construído para dados internos no espaço do Rust. As mutações em instâncias dessa classe não serão refletidas no circuito. Em geral, não é possível alterar diretamente os contextos de instrução que já estão no circuito; a QuantumCircuit a interface foi projetada para armazenar e construir circuitos, enquanto o transpilador e suas passagens, e sua representação intermediária, são onde você deve procurar uma interface para alterar circuitos DAGCircuit são onde você deve procurar uma interface para alterar circuitos.
Os QuantumCircuit que adicionam instruções aos circuitos (como append()e todos os métodos auxiliares de porta padrão) retornam um InstructionSetque é um identificador de vários CircuitInstructions simultaneamente.
InstructionSet(*[, resource_requester] ) | Coleção de instruções e seus contextos. |
Esse InstructionSet agora é pouco usado no Qiskit. Ele fornece um conjunto mínimo de métodos para realizar mutações pós-append nas instruções (que serão propagadas para o circuito), mas agora elas são desencorajadas e você deve usar as alternativas indicadas nesses métodos.
Operações, instruções e portas
Dentro de um CircuitInstructiona interface mínima que qualquer operação deve cumprir é Operation. Essa é uma visão de nível muito alto e só pode ser usada para circuitos abstratos. O principal objetivo de tratar as operações como Operation é permitir que objetos matemáticos arbitrários (como quantum_info.Operator) sejam adicionados diretamente aos circuitos abstratos.
Operation() | Interface de operação quântica. |
A maioria das operações, incluindo todas as operações em circuitos físicos, são instâncias da definição mais concreta de Instruction. Isso representa qualquer instrução que alguma QPU pode ser capaz de executar nativamente, como Measure. Instruction não precisa ser unitária (assim como Measure não é); uma instrução é especificamente unitária se for uma instrução Gate.
Instruction(name, num_qubits, num_clbits, params) | Instrução quântica genérica. |
Instructions podem ser quase arbitrários, desde que atuem apenas em Qubits e Clbits, e sejam parametrizados por seus paramseles não devem tentar "fechar" os registros do circuito externo ou usar parâmetros ocultos dentro deles mesmos. Instructions podem ser relacionados a outros circuitos para fornecer uma decomposição usando seu atributo Instruction.definition que fornece uma decomposição local e única. Isso pode ser feito em qualquer conjunto de operações que lhe seja mais conveniente, desde que as definições de todos os portões contidos tenham alguma ordem topológica; ou seja, não é possível usar um portão em uma definição se sua própria definição depender do pai. Se o Instruction deve ser considerado totalmente opaco para os otimizadores, seu definition pode ser None. Consulte Criação de instruções personalizadas para obter mais detalhes.
O params de uma instrução pode ser tecnicamente arbitrário, mas, em geral, você deve tentar se ater a parametrizações em termos de números reais, sempre que possível. O próprio Qiskit quebra essa regra em muitos lugares, e você encontrará todos os tipos incomuns nos campos Instruction.params mas eles são uma fonte incômoda de bugs, pois geralmente implicam a necessidade de um revestimento especial sensível ao tipo. Se a sua instrução for parametrizada em termos de ângulos, você poderá usar de forma confiável a parametrização em tempo de compilação nela, e ela se integrará bem com o QuantumCircuit.assign_parameters().
Embora Instruction não seja necessariamente unitário, sua subclasse Gate implica unitariedade e acrescenta to_matrix() e control() a todos os métodos herdados de Instruction.
Gate(name, num_qubits, params[, label] ) | Portão unitário. |
Gate herda todos os métodos de Instruction e todas as mesmas considerações sobre seus métodos params e definition exceto, é claro, pelo fato de que Gates não pode atuar em nenhum recurso clássico.
Gate podem (e devem) ter uma base definitionmas você também pode especificar várias decomposições diferentes em bases diferentes usando um EquivalenceLibrary.
Subclasse Gateo Qiskit tem uma classe especial ControlledGate especial também. Essa classe é a base de muitas portas da biblioteca padrão que são controladas (como CXGate), que é onde você provavelmente a encontrará:
ControlledGate(name, num_qubits, params[,...] ) | Portão unitário controlado. |
Cada um dos Instruction, Gate e ControlledGate tem um tipo singleton correspondente, construído usando o mecanismo descrito em qiskit.circuit.singleton. A documentação em nível de módulo contém detalhes completos, juntamente com descrições de SingletonInstruction, SingletonGate e SingletonControlledGate. Do ponto de vista do usuário, pouca coisa muda com base no fato de a classe base ser um singleton ou não; a intenção é sempre a de chamar to_mutable() primeiro se precisar obter uma cópia de propriedade segura para mutação de uma instrução (não se pode presumir que uma instrução arbitrária seja mutável) e, embora a inspeção direta do type seja desencorajada, se você precisar dela, a maneira confiável de encontrar o tipo "base" de uma instrução potencialmente singleton é usar o base_class.
ControlledGate usa os mesmos mecanismos das portas de subclasse para definir uma síntese fixa e preguiçosa para si mesmo. Naturalmente, isso não é sensível ao hardware e é mais difícil de conectar às rotinas de síntese do compilador, mas funciona melhor como um Instruction que poderia ser executado nativamente no hardware. Para os casos em que a síntese e a otimização abstrata são mais importantes, o Qiskit oferece uma classe componível chamada AnnotatedOperationque rastreia "modificadores de porta" (dos quais ControlModifier é um deles) a ser aplicado ao base_op.
AnnotatedOperation(base_op, modificadores) | Operação anotada. |
Os modificadores disponíveis para AnnotatedOperation são:
InverseModifier
class qiskit.circuit.InverseModifier
Bases: Modifier
Modificador inverso: especifica que a operação é invertida.
ControlModifier
class qiskit.circuit.ControlModifier(num_ctrl_qubits=0, ctrl_state=None)
Bases: Modifier
Modificador de controle: especifica que a operação é controlada por num_ctrl_qubits e tem estado de controle ctrl_state.
Parâmetros
PowerModifier
class qiskit.circuit.PowerModifier(power)
Bases: Modifier
Modificador de potência: especifica que a operação é elevada à potência power.
Parâmetros
power (float)
Para obter informações sobre como criar portas e instruções personalizadas, incluindo como criar objetos únicos e portas paramétricas reutilizáveis por meio de subclasses, consulte Criação de instruções personalizadas abaixo. A biblioteca de circuitos do Qiskit em qiskit.circuit.library contém muitas portas e circuitos predefinidos para você usar.
Instruções especiais integradas
O Qiskit contém algumas Instruction classes que são, de certa forma, "especiais". Normalmente, eles têm um tratamento especial no código do circuito, no transpilador ou nos modelos de hardware. Em geral, todas essas são instruções com as quais você já deve estar familiarizado.
As medições no Qiskit são de um único Qubit em um único Clbit. Esses são os dois aos quais a instrução é aplicada. As medições estão na base computacional.
Medida
class qiskit.circuit.Measure(label=None)
Bases: SingletonInstruction
Medição quântica na base computacional.
Parâmetros
rótulo - rótulo de cadeia de caracteres opcional para essa instrução.
Em relação às medições, há uma operação Reset que não produz dados clássicos, mas instrui o hardware a retornar o qubit ao estado . Supõe-se que isso ocorra de forma incoerente e colapse qualquer emaranhamento.
Reconfigurar
class qiskit.circuit.Reset(label=None)
Bases: SingletonInstruction
Redefinir incoerentemente um qubit para o estado .
Parâmetros
rótulo - rótulo de cadeia de caracteres opcional dessa instrução.
O hardware pode ser instruído a aplicar um período de inatividade em tempo real em um determinado qubit. Um circuito programado (consulte qiskit.transpiler) incluirá todos os tempos ociosos nos qubits explicitamente em termos de Delay. BoxOp também pode ter uma duração explícita anexada, em seu campo BoxOp.duration .
Atraso
class qiskit.circuit.Delay(duration, unit=None)
Bases: Instruction
Não faz nada e apenas atrasa/espera/ocupa por um período especificado.
Parâmetros
- duração - o período de tempo da duração. Se for um
Exprele deve ser uma expressão constante do tipoDuratione o parâmetrounitdeve ser omitido (ou DEVE ser "expr" se for especificado). - unit - a unidade da duração, se
durationfor um valor numérico. Deve ser"dt", uma unidade de segundos com prefixo SI ou "expr".
Aumentos
CircuitError - Uma expressão duration foi especificada com um tipo resolvido que não é baseado em tempo, ou o unit foi especificado incorretamente.
As durações de atraso podem ser especificadas com tempos concretos e constantes ou com "expressões de duração" de resolução atrasada criadas a partir de expr.Stretch objetos. Consulte Programação de resolução atrasada para saber mais sobre isso.
A instrução Barrier pode abranger um número arbitrário de qubits e clbits, e é um no-op no hardware. No entanto, durante a transpilação e a otimização, ele impede que qualquer otimização "atravesse" a barreira, ou seja, na:
from qiskit.circuit import QuantumCircuit
qc = QuantumCircuit(1)
qc.x(0)
qc.barrier()
qc.x(0)é proibido que o otimizador cancele as duas instruções .
Barreira
class qiskit.circuit.Barrier(num_qubits, label=None)
Bases: Instruction
Uma diretriz para a compilação de circuitos para separar partes de um circuito de modo que quaisquer otimizações ou reescritas sejam restritas a agir somente entre barreiras.
Isso também aparecerá nas visualizações como um marcador visual.
Parâmetros
A instrução Store é particularmente especial, pois permite escrever o resultado de uma expressão de computação clássica em tempo real (um expr.Expr) em uma variável clássica local (a expr.Var). Não é preciso nem Qubit nem Clbit mas tem um operador explícito lvalue e rvalue.
Por exemplo, para determinar a paridade de um bitstring cr e armazená-lo em outro registro creg, a instrução Store pode ser usada da seguinte forma:
parity = expr.lift(cr[0])
for i in range(1,n):
parity = expr.bit_xor(cr[i], parity)
qc.store(creg[0], parity)Loja
class qiskit.circuit.Store(lvalue, rvalue)
Bases: Instruction
Um armazenamento manual de algum valor clássico em um local de memória clássico.
Essa é uma primitiva de baixo nível do tratamento de expressões clássicas (semelhante a como Measure é um primitivo para medição quântica) e não é seguro para subclasses.
Parâmetros
- lvalue (expr.Expr) – o local da memória em que está sendo armazenado.
- rvalue (expr.Expr) – o resultado da expressão que está sendo armazenado.
lvalue
Obtém o nó de valor l Expr que está sendo armazenado.
rvalue
Obter o nó r-value Expr que está sendo gravado no valor l.
Computação clássica em tempo real
Documentação em nível de módulo sobre como funcionam os sistemas de variáveis, expressões e tipos, os objetos usados para representá-los e as operações clássicas disponíveis.
Trabalhar com dados clássicos digitados em tempo real
Os QuantumCircuit métodos para trabalhar com essas variáveis no contexto de um único circuito.
O Qiskit tem suporte rudimentar de baixo nível para representar cálculos clássicos em tempo real, que ocorrem durante a execução da QPU e afetam os resultados. Ainda estamos relativamente no início do suporte de hardware para esses conceitos, portanto, esteja ciente de que você precisará trabalhar em conjunto com a documentação do seu fornecedor de hardware para obter o melhor uso de qualquer computação clássica em tempo real.
Esses cálculos em tempo real são representados pelo sistema de expressão e tipo em qiskit.circuit.classical. Em um alto nível, todas as expressões em tempo real são representadas por um nó Expr que faz parte de uma representação de "árvore" de expressão, que tem um Type bem definido associado a ela em todos os níveis. Consulte a documentação em nível de módulo para obter muito mais detalhes sobre as representações internas dessas classes.
O resultado de um teste em tempo real Expr pode ser usado diretamente em determinados locais. Atualmente, isso está limitado a condições de IfElseOp e WhileLoopOpe o alvo de SwitchCaseOp. O resultado também pode ser armazenado em um local de armazenamento clássico digitado, usando a instrução Store (ou seu construtor QuantumCircuit.store() ), apoiada por um nó expr.Var nó.
Um circuito pode conter locais de armazenamento clássicos manuais, representados internamente pelo Var nó da Expr árvore. Elas têm um tipo clássico anexado (como qualquer outra expressão). Eles podem ser declarados e inicializados em cada execução do circuito (add_var()) ou ser entradas para o circuito (add_input()).
Parametrização em tempo de compilação
Várias instâncias paramétricas Instruction no Qiskit podem ser parametrizadas de maneiras que são projetadas para serem resolvidas no momento da compilação. Eles são caracterizados pelo uso do Parameter e ParameterExpression classes.
Parameter(nome[, uuid] ) | Um parâmetro simbólico em tempo de compilação. |
ParameterExpression( [nome_mapa, expressão] ) | Uma expressão de parâmetro. |
A principal diferença entre isso e as variáveis expr.Var usadas na computação clássica em tempo real é que ParameterExpression é uma representação simbólica de uma expressão matemática. A semântica da expressão é a da matemática regular sobre os números reais contínuos (e, em casos limitados, sobre os números complexos). Em contraste, Var é um identificador de uma variável armazenada em um computador clássico, como um valor de ponto flutuante ou um número inteiro de largura fixa, que são sempre discretos.
Em outras palavras, você pode esperar ParameterExpression faça simplificações simbólicas que são válidas em matemática, como a simplificação de . Essa simplificação não é válida na aritmética de ponto flutuante, e expr.Expr não fará isso.
A parte de "tempo de compilação" desses parâmetros significa que, normalmente, você desejará "atribuir" valores aos parâmetros antes de enviar o circuito para execução. Esses parâmetros normalmente podem ser usados em qualquer lugar que espere um ângulo matemático (como os parâmetros de uma porta de rotação), com a ressalva de que o hardware normalmente exigirá que eles sejam atribuídos a um valor tipado classicamente adequado antes da execução. Você pode fazer essa tarefa usando QuantumCircuit.assign_parameters().
Talvez você queira usar muitos parâmetros que estejam relacionados entre si. Para facilitar isso (e para evitar que você precise inventar muitos nomes), você pode usar o construtor de conveniência ParameterVector. Os elementos do vetor são todos instâncias válidas Parameter de uma subclasse especial ParameterVectorElement.
ParameterVector(nome[, comprimento] ) | Um contêiner de muitos objetos relacionados Parameter objetos relacionados. |
ParameterVectorElement(vetor, índice[, uuid] ) | Um elemento de um ParameterVector. |
Controle de fluxo em circuitos
Dentro de QuantumCircuito fluxo de controle clássico é representado por Instructions específicos, que são subclasses de ControlFlowOp.
ControlFlowOp(*args, **kwargs) | Classe abstrata para encapsular todas as operações de fluxo de controle. |
Por conveniência, há uma instância frozenset instância que contém os atributos Instruction.name atributos de cada uma das operações de fluxo de controle.
qiskit.circuit.CONTROL_FLOW_OP_NAMES
Conjunto de nomes de instruções das operações de fluxo de controle conhecidas do Qiskit.
A função get_control_flow_name_mapping() permite acessar as classes de operação de fluxo de controle associadas a cada nome.
get_control_flow_name_mapping
qiskit.circuit.get_control_flow_name_mapping()
Retorna um dicionário que mapeia os nomes das operações de fluxo de controle para suas classes correspondentes."
Exemplos
from qiskit.circuit import get_control_flow_name_mapping
ctrl_flow_name_map = get_control_flow_name_mapping()
if_else_object = ctrl_flow_name_map["if_else"]
print(if_else_object)<class 'qiskit.circuit.controlflow.if_else.IfElseOp'>Essas operações de fluxo de controle (IfElseOp, WhileLoopOp, SwitchCaseOp ForLoopOp, e BoxOp) têm um estado específico que define as condições e estratégias de ramificação, mas contêm todos os diferentes blocos de subcircuitos que podem ser inseridos em sua blocks propriedade.
IfElseOp(condição, verdadeiro_corpo[, falso_corpo,...] ) | Uma operação de circuito que executa um programa (true_body) se uma condição fornecida (condition) for avaliada como verdadeira e, opcionalmente, avalia outro programa (false_body) caso contrário. |
WhileLoopOp(condição, corpo[, rótulo] ) | Uma operação de circuito que executa repetidamente um subcircuito (body) até que uma condição (condition) seja avaliada como falsa. |
SwitchCaseOp(alvo, casos, *[, rótulo] ) | Uma operação de circuito que executa um bloco de circuito específico com base na correspondência de um determinado target com uma lista ordenada de values. |
ForLoopOp(indexset, loop_parameter, body[,...] ) | Uma operação de circuito que executa repetidamente um subcircuito (body) parametrizado por um parâmetro loop_parameter por meio do conjunto de valores inteiros fornecidos em indexset. |
BoxOp(corpo[, duração, unidade, rótulo, anotações] ) | Uma "caixa" com escopo de operações em um circuito que são tratadas atomicamente no contexto maior. |
O SwitchCaseOp também entende um valor especial:
qiskit.circuit.CASE_DEFAULT
Valor Padrão: <default case>
Um objeto especial que representa o caso "padrão" de uma instrução switch. Se você usar isso como um caso-alvo, ele deverá ser o último caso e corresponderá a qualquer coisa que ainda não tenha sido correspondida. Ao usar a interface do construtor de QuantumCircuit.switch()isso também pode ser acessado como o atributo DEFAULT do objeto case-builder vinculado.
Além das operações de fluxo de controle da estrutura de blocos, há também duas instruções especiais que afetam o fluxo de controle dentro de loops. Elas correspondem aos usos típicos das declarações break e continue nas linguagens de programação clássicas.
BreakLoopOp(num_qubits, num_clbits[, rótulo] ) | Uma operação de circuito que, quando encontrada, salta para o final do loop adjacente mais próximo. |
ContinueLoopOp(num_qubits, num_clbits[, rótulo] ) | Uma operação de circuito que, quando encontrada, passa para a próxima iteração do loop adjacente mais próximo. |
As representações das classes estão documentadas aqui, mas observe que a construção manual dessas classes é uma operação de baixo nível que não esperamos que os usuários precisem fazer com frequência.
Os usuários devem ler Adicionando fluxo de controle a circuitos para conhecer os fluxos de trabalho recomendados para a criação de circuitos habilitados para fluxo de controle.
Como as ControlFlowOp também são subclasses Instruction isso significa que a forma como elas são armazenadas nas instâncias CircuitInstruction os tem "aplicados" a uma sequência de qubits e clbits em suas instâncias qubits e clbits atributos. Isso pode levar a problemas sutis de coerência de dados: o Qubit e Clbit usados dentro dos blocos de subcircuito das operações de fluxo de controle não serão necessariamente idênticos aos objetos correspondentes no bloco de controle CircuitInstruction. Qualquer código que consuma operações de fluxo de controle no Qiskit precisa estar ciente disso; dentro de um subcircuito, você deve tratar subcircuit.qubits[i] como se fosse realmente outer_instruction.qubits[i], e assim por diante. Você pode gerar uma tabela de pesquisa fácil para isso:
cf_instruction: CircuitInstruction = ...
cf_operation: ControlFlowOp = cf_instruction.operation
for block in blocks:
# Mappings of "inner" qubits/clbits to the outer ones.
qubit_map = dict(zip(block.qubits, cf_instruction.qubits))
clbit_map = dict(zip(block.clbits, cf_instruction.clbits))
# ... do something with `block` ...Lembre-se de que você precisará propagar essas informações se recorrer a sub-blocos de operações de fluxo de controle.
Todos os blocos de subcircuito em um ControlFlowOp devem conter os mesmos números de Qubits e Clbits, referindo-se aos mesmos bits externos na mesma ordem, de modo que o loop zip fornecido no bloco de código acima funcione. Os objetos do circuito interno Bit não precisam ser literalmente os mesmos objetos. Ao usar a interface do construtor de fluxo de controle (que, nunca é demais enfatizar, é altamente recomendada para os usuários), os construtores providenciarão para que os objetos de bits internos sejam idênticos aos objetos de bits externos; o qubit_map no bloco de código acima será sempre um mapeamento {x: x}, mas se você estiver consumindo os blocos, deverá estar preparado para o caso de o mapeamento ser necessário.
Qualquer ClassicalRegisters usados em um subcircuito de fluxo de controle também devem estar presentes em todos os blocos que o contêm (ou seja, qualquer bloco que contenha operações de fluxo de controle e o circuito mais externo), e todos os blocos do mesmo ControlFlowOp precisam conter os mesmos registros. Mais uma vez, a interface do construtor providenciará para que esse seja o caso (ou produzirá um erro de eager se não for possível).
Quando a construção de baixo nível estiver sendo usada, os blocos QuantumCircuit devem fechar manualmente todas as variáveis de computação clássica em tempo real de escopo externo que estiverem usando. Isso é marcado por eles estarem no iter_captured_vars() para esse bloco. As bibliotecas que constroem esses blocos manualmente precisarão rastrear essas capturas ao construir blocos de circuito de fluxo de controle e adicioná-las ao bloco usando add_capture() (ou o argumento do construtor captures ), mas o código do usuário normalmente usará a interface do construtor de fluxo de controle, que lida com isso automaticamente.
Consulte a documentação de construção de fluxo de controle para obter mais informações sobre como construir circuitos com fluxo de controle.
Anotações locais de instrução
A discussão em nível de módulo da estrutura de anotação, incluindo como definir anotações personalizadas e como o sistema interage com o compilador e com a serialização para outros formatos.
Determinadas instruções de circuito podem ser "anotadas" com anotações locais de instrução. A partir do Qiskit 2.1.0, isso está limitado a BoxOp. Todas as anotações são subclasses de um objeto básico de definição de interface, mas normalmente representam análises e comandos totalmente personalizados.
Annotation(*args, **kwargs) | Uma anotação arbitrária para instruções. |
Investigando relações de comutação
Se duas operações em um circuito forem complementares, podemos trocar a ordem em que elas são aplicadas. Isso pode permitir otimizações e simplificações, por exemplo, se permitir mesclar ou cancelar portas:
┌─────────┐ ┌─────────┐ ┌─────────┐
q_0: ┤ Rz(0.5) ├──■──┤ Rz(1.2) ├──■── q_0: ┤ Rz(1.7) ├
└─────────┘┌─┴─┐└──┬───┬──┘┌─┴─┐ = └──┬───┬──┘
q_1: ───────────┤ X ├───┤ X ├───┤ X ├ q_1: ───┤ X ├───
└───┘ └───┘ └───┘ └───┘A execução dessas otimizações faz parte do transpilador, mas as ferramentas para investigar as comutações estão disponíveis no arquivo CommutationChecker.
CommutationChecker([...]) | Verifique as comutações de duas operações. |
Agendamento de resolução atrasada
Normalmente, a saída do compilador do Qiskit não pode ser executada diretamente em uma QPU. Primeiro, é provável que ele passe por algum compilador de nível de pulso específico do fornecedor, que converte as portas e as medições em sinais para os componentes eletrônicos de controle da QPU. Embora o Qiskit's Target possa representar algumas das restrições de tempo que esses pulsos terão, ele geralmente não é totalmente completo. Isso é especialmente verdadeiro quando circuitos dinâmicos (operações de alimentação) estão envolvidos; os atrasos induzidos pelos componentes clássicos geralmente dependem de detalhes de pós-otimização de baixo nível dos compiladores de back-end e não podem ser conhecidos pelo Qiskit.
Nessas situações, um usuário ainda pode exercer controle sobre a programação relativa de pulsos, como no caso de desacoplamento dinâmico, usando durações "esticadas". Eles são construídos por QuantumCircuit.add_stretch()e interagem com o sistema de expressão clássica descrito em qiskit.circuit.classicalembora não sejam mutáveis em tempo real.
Por exemplo, podemos adicionar trechos e caixas para configurar um sistema em que duas sequências separadas de desacoplamento dinâmico são aplicadas ao mesmo qubit, enquanto um par de outros qubits sofre um atraso de duração desconhecida. As duas sequências são obrigadas a ter o mesmo comprimento, mesmo que internamente os pulsos DD concretos tenham comprimentos diferentes.
from qiskit import QuantumCircuit
from qiskit.circuit.classical import expr
qc = QuantumCircuit(3, 3)
# This sets up three duration "degrees of freedom" that will
# be resolved later by a backend compiler.
a = qc.add_stretch("a")
b = qc.add_stretch("b")
c = qc.add_stretch("c")
# This set of operations involves feed-forward operations that
# Qiskit cannot know the length of.
with qc.box():
qc.h(1)
qc.cx(1, 2)
qc.measure([1, 2], [1, 2])
with qc.if_test(expr.equal(qc.clbits[1], qc.clbits[2])):
qc.h(1)
# While that stuff is happening to qubits (1, 2), we want
# qubit 0 to do two different DD sequences. The two DD
# sequences are fixed to be the same length as each other,
# even though they're both internally stretchy.
with qc.box(duration=a):
# Textbook NMRish XX DD.
qc.delay(b, 0)
qc.x(0)
qc.delay(expr.mul(2, b), 0)
qc.x(0)
qc.delay(b, 0)
with qc.box(duration=a):
# XY4-like DD.
for _ in range(2):
qc.delay(c, 0)
qc.y(0)
qc.delay(expr.mul(2, c), 0)
qc.x(0)
qc.delay(c, 0)Criando instruções personalizadas
Se você deseja criar instruções ou portas únicas e simples que serão adicionadas a um circuito, e os blocos estão sendo usados apenas para fins de visualização ou agrupamento, a maneira mais fácil de criar uma instrução ou porta personalizada é simplesmente criar sua definição como um bloco QuantumCircuite, em seguida, usar seus blocos to_instruction() ou to_gate() conforme apropriado. Os resultados podem ser fornecidos diretamente para QuantumCircuit.append() no circuito maior. Esses métodos criarão os arquivos base Instruction ou Gate cujo atributo definition é o circuito fornecido, o que significa que ele estará automaticamente acessível ao transpilador e a outras funções do Qiskit que tentam decompor circuitos.
Observe que as instruções e portas autônomas devem atuar apenas em qubits e clbits; as instruções que precisarem usar um fluxo de controle complexo precisarão ser incorporadas ao QuantumCircuit usando compose().
Criando subclasses de instrução
As classes de base Instruction, Gate e ControlledGate são todas projetadas para serem seguras para subclasses e têm pontos de conexão para as subclasses implementarem. Se sua porta personalizada for sem parâmetros e sem estado, talvez você também queira derivar da classe singleton correspondente em qiskit.circuit.singletoncomo, por exemplo, SingletonGate. Você deve consultar a documentação em qiskit.circuit.singleton para obter métodos adicionais e pontos de conexão para o mecanismo singleton.
Normalmente, as subclasses devem definir um construtor padrão que chame o construtor :class`super` com os argumentos corretos para sua instrução. É permitido ter um estado extra na classe, mas suas subclasses se integrarão de forma mais confiável ao restante do maquinário do Qiskit se você depender apenas do seu parâmetro Instruction.paramse esses parâmetros forem puramente ângulos de porta.
As subclasses de Instruction (ou uma de suas subclasses) devem implementar o método privado Instruction._define() que preenche de forma preguiçosa o cache _definition oculto que dá suporte ao método público definition público.
_define
Instruction._define()
Preencher o campo _definition armazenado em cache desse Instruction.
As subclasses devem implementar esse método para proporcionar uma construção preguiçosa de seu atributo público definition de seus atributos públicos. Uma subclasse pode usar seu params no momento da chamada. O método deve preencher o site _definition com um QuantumCircuit e não retornar um valor.
Nas subclasses de ControlledGateo método _define() deve implementar a decomposição apenas para o estado de controle "all-ones". A ControlledGate.definition modificará isso para lidar com o estado de controle real.
Se a subclasse estiver usando o mecanismo de singleton, saiba que _define() será chamado imediatamente após a execução da instrução do corpo da classe, para produzir o objeto de definição do objeto singleton canônico. Isso significa que a sua definição só deve usar portas que já estejam definidas; se estiver escrevendo uma biblioteca com muitas portas individuais, será necessário ordenar os arquivos e as importações para garantir que isso seja possível.
As subclasses de Gate provavelmente também desejarão substituir o método de instância do protocolo de matriz do Numpy, __array__. Isso é usado por Gate.to_matrix()e tem a assinatura:
__array__
object.__array__(dtype=None, copy=None)
Retorna uma matriz Numpy que representa o portão. Isso pode usar o campo params e pode presumir que esses são valores numéricos (supondo que a subclasse espere isso) e não parâmetros de tempo de compilação.
Para maior eficiência, a matriz retornada deve ter como padrão um dtype de complex.
Se a sua subclasse personalizada tiver representações naturais de suas formas controladas ou inversas, talvez você também queira substituir as funções inverse() e control() métodos.
Como exemplo de definição de uma porta personalizada, ou seja, uma rotação de ângulo único em torno do eixo . Isso é essencialmente RZXGate, se os qubits fossem ao contrário, portanto, escreveremos nossa definição em termos disso. Somos paramétricos, portanto não podemos ser um singleton, mas somos unitários, portanto devemos ser um Gate:
import math
import numpy as np
from qiskit.circuit import Gate, QuantumCircuit
class RXZGate(Gate):
def __init__(self, theta):
# Initialize with our name, number of qubits and parameters.
super().__init__("rxz", 2, [theta])
def _define(self):
# Our base definition is an RZXGate, applied "backwards".
defn = QuantumCircuit(2)
defn.rzx(1, 0)
self._definition = defn
def inverse(self, annotated = False):
# We have an efficient representation of our inverse,
# so we'll override this method.
return RXZGate(-self.params[0])
def power(self, exponent: float):
# Also we have an efficient representation of power.
return RXZGate(exponent * self.params[0])
def __array__(self, dtype=None, copy=None):
if copy is False:
raise ValueError("unable to avoid copy while creating an array as requested")
cos = math.cos(0.5 * self.params[0])
isin = 1j * math.sin(0.5 * self.params[0])
return np.array([
[cos, -isin, 0, 0],
[-isin, cos, 0, 0],
[0, 0, cos, isin],
[0, 0, isin, cos],
], dtype=dtype)Neste exemplo, definimos uma definição de base em termos de RZXGate, mas, para permitir decomposições mais rápidas em uma série de bases, talvez queiramos adicionar mais algumas equivalências a SessionEquivalenceLibrary. Observe que a BasisTranslator pesquisará todas as equivalências possíveis em todas as profundidades possíveis, portanto, fornecer uma equivalência em termos de (digamos) XGate automaticamente tornará as decomposições em termos de RXGate também estarão disponíveis.
Vamos adicionar uma equivalência em termos de , e para um parâmetro simbólico arbitrário:
from qiskit.circuit import SessionEquivalenceLibrary, Parameter
theta = Parameter("theta")
equiv = QuantumCircuit(2)
equiv.h(0)
equiv.cx(1, 0)
equiv.rz(theta, 0)
equiv.cx(1, 0)
equiv.h(0)
SessionEquivalenceLibrary.add_equivalence(RZXGate(theta), equiv)Depois disso, durante toda a sessão do intérprete Python, os tradutores como BasisTranslator encontrarão nossa nova definição em sua pesquisa.
Trabalhando com objetos em nível de circuito
Conversão de circuitos abstratos em circuitos físicos
Um resumo QuantumCircuit não pode ser executado de forma confiável no hardware. Talvez você possa usar alguns dos simuladores de alto nível vinculados em Simulação de circuitos para produzir resultados rápidos para circuitos de pequena escala, mas para executar circuitos em escala de utilidade pública, você precisará usar hardware real, o que envolve a compilação para um circuito físico.
A função de alto nível para fazer isso é transpile()ela recebe um circuito abstrato e um hardware backend ou target, e retorna um circuito físico. Para obter mais acesso e controle sobre os estágios dos passes que serão executados, use generate_preset_pass_manager() para criar uma StagedPassManager primeiro, que você pode modificar depois.
O maquinário completo de transpilação e compilação está descrito em detalhes na documentação do módulo qiskit.transpiler e os detalhes de todas as passagens incorporadas ao Qiskit estão disponíveis em qiskit.transpiler.passes.
Simulação de circuitos
Embora não faça parte da qiskit.circuit uma das necessidades mais comuns é obter resultados rápidos de simulação para QuantumCircuit objetos. Esta seção fornece um ponto de partida rápido para outros locais na documentação para encontrar as informações relevantes.
Para circuitos unitários, você pode simular os efeitos no estado passando o QuantumCircuit diretamente para o Statevector construtor padrão. De modo semelhante, você pode obter uma matriz unitária que representa o circuito como um operador, passando-a para o construtor Operator construtor padrão. Se você tiver um circuito físico, talvez queira passá-lo para o método Operator.from_circuit() para aplicar as transformações do QuantumCircuit.layout para mapeá-lo de volta ao espaço "abstrato" do qubit.
Para uma experiência de simulação mais parecida com o backend, há implementações apoiadas por simuladores de todas as interfaces de hardware do Qiskit. Em particular, você pode estar interessado em:
BasicProvidere os back-ends brutos que ele pode retornar para você.StatevectorSimulatorpara um invólucro do tipo backend em torno deStatevector- O
qiskit_aerpara recursos de simulação completos e de alto desempenho. StatevectorSamplereStatevectorEstimatorpara implementações de referência apoiadas por simuladores das primitivas Qiskit.
Definindo relações de equivalência
Uma tarefa comum no mapeamento de circuitos abstratos para o hardware físico e na otimização do resultado é encontrar relações de equivalência que mapeiem uma porta para um conjunto de bases diferente. O Qiskit armazena essas informações em uma classe de banco de dados chamada EquivalenceLibrary.
EquivalenceLibrary( [base] ) | Uma biblioteca que fornece um mapeamento unidirecional de Gates para suas implementações equivalentes como QuantumCircuits. |
O Qiskit é fornecido com um grande conjunto de relações de equivalência predefinidas para todas as suas portas padrão. Essa biblioteca básica é chamada StandardEquivalenceLibrarye deve ser tratada como imutável.
qiskit.circuit.StandardEquivalenceLibrary
A EquivalenceLibrary que armazena todas as relações de porta padrão incorporadas do Qiskit. Você não deve alterar isso, mas sim criar seu próprio EquivalenceLibrary usando este como seu base, ou modificar o estado global SessionEquivalenceLibrary.
O Qiskit também define um objeto de estado global compartilhado, SessionEquivalenceLibrary , que é a equivalência padrão usada por vários lugares no Qiskit, principalmente a passagem do transpilador BasisTranslator . Sinta-se à vontade para adicionar suas próprias equivalências a isso usando o método add_equivalence(), e elas serão automaticamente capturadas pelas instâncias padrão do BasisTranslator .
qiskit.circuit.SessionEquivalenceLibrary
A instância padrão de EquivalenceLibraryque será usada pela maioria dos objetos do Qiskit se nenhuma biblioteca for especificada manualmente. Você pode ficar à vontade para adicionar equivalências a isso usando add_equivalence(). Ele herda todas as regras incorporadas do StandardEquivalenceLibrary.
Aplique o efeito Pauli a um circuito
Há dois tipos principais de ruído na execução de circuitos quânticos. O primeiro é o ruído estocástico, ou incoerente, que se deve principalmente à interação indesejada entre o processador quântico e o ambiente externo no qual ele reside. O segundo é conhecido como erro coerente, e esses erros surgem devido ao controle imperfeito de um sistema quântico. Podem ser termos indesejados em um sistema hamiltoniano, ou seja, evolução unitária incorreta, ou erros de controle temporal incorreto do sistema quântico, o que inclui coisas como formas de pulso incorretas para portas.
O giro de Pauli é uma técnica de supressão de erros quânticos que usa a randomização para transformar erros coerentes em erros estocásticos, combinando os resultados de muitos circuitos aleatórios, mas logicamente equivalentes. O Qiskit fornece uma função para aplicar o giro de Pauli a um determinado circuito para portas padrão de dois qubits. Para obter mais detalhes, consulte a documentação da função abaixo:
pauli_twirl_2q_gates
qiskit.circuit.pauli_twirl_2q_gates(circuit, twirling_gate=None, seed=None, num_twirls=None, target=None)
Crie cópias de um determinado circuito com o giro de Pauli aplicado em torno de duas portas de qubit especificadas.
Se você estiver executando essa função com a intenção de girar um circuito para ser executado no hardware, essa pode não ser a maneira mais eficiente de executar o giro. Especialmente se o fornecedor de hardware tiver implementado a primitives com SamplerV2 e EstimatorV2 , essa provavelmente não é a melhor maneira de aplicar o twirling ao seu circuito e você deverá consultar a implementação de SamplerV2 e/ou EstimatorV2 para o fornecedor de hardware especificado.
Se a intenção dessa função for ser executada após transpile() ou PassManager.run() o argumento opcional target poderá ser usado para que os portões Pauli de 1 qubit inseridos sejam sintetizados para serem compatíveis com o dado Target para que o(s) circuito(s) de saída ainda seja(m) compatível(is).
Parâmetros
- circuit (QuantumCircuit) – O circuito para girar
- twirling_gate (None | str |Gate |list[str] | list[Gate]) – O portão a ser girado, o padrão é None, o que significa girar todos os portões padrão:
CXGate,CZGate,ECRGate, eiSwapGate. Se fornecido, pode ser um único portão ou uma lista de portões como um objeto de portão ou seu nome de cadeia de caracteres. Atualmente, somente os nomes "cx", "cz", "ecr" e "iswap" são compatíveis. Se um objeto de portão for fornecido fora dos portões padrão, ele deverá ter uma matriz definida em seu métodoto_matrixpara que o portão possa ser girado. Se uma configuração de giro válida não puder ser calculada, esse portão específico será silenciosamente ignorado e não será girado. - seed (int | None) – Uma semente inteira para o gerador de números aleatórios usado internamente por essa função. Se especificado, deve estar entre 0 e 18.446.744.073.709.551.615.
- num_twirls (int | None) – O número de circuitos giratórios a serem construídos. O padrão é
Nonee retornará um único circuito. Se for um número inteiro, será retornada uma lista de circuitos com circuitos num_twirls. - target (Target | None) – Se for especificada uma instância
Targetinstância a ser usada para executar a decomposição de um único qubit como parte do giro de Pauli para otimizar e mapear as portas pauli adicionadas ao circuito para o destino especificado.
Retorna
Uma cópia do circuito fornecido com o giro de Pauli aplicado a cada instância da porta de giro especificada.
Tipo de retorno
Exceções
Quase todas as funções e métodos de circuito gerarão um erro CircuitError quando encontrarem um erro específico para o uso do Qiskit (ao contrário de problemas regulares de digitação ou indexação, que normalmente geram o erro padrão correspondente em Python ).
CircuitError
exception qiskit.circuit.CircuitError(*message)
Bases: QiskitError
Classe base para erros gerados durante o processamento de um circuito.
Defina a mensagem de erro.
Convenções do circuito
Ao construir circuitos a partir de objetos abstratos e matrizes mais concretas, há várias convenções possíveis sobre a rotulagem de bits, a ordenação de bits e como o produto tensor abstrato é realizado na álgebra matricial concreta.
As convenções da Qiskit são:
- em representações de cadeias de bits, os bits são rotulados com o bit mais à direita na cadeia chamada e o bit mais à esquerda na cadeia de bits chamada .
- ao usar números inteiros como índices de especificadores de bits em funções de construção de circuitos, o número inteiro é tratado como um índice em
QuantumCircuit.qubits(ouclbits). - ao desenhar circuitos, colocamos os bits de menor índice no topo.
- em representações de vetores de estado, realizamos o produto tensorial abstrato como o produto de Kronecker e ordenamos os argumentos para isso de modo que a amplitude do estado de base computacional , em que é a cadeia de bits interpretada como um número inteiro, esteja no local
statevector[x]. - ao controlar um portão, o(s) qubit(s) de controle é(são) colocado(s) em primeiro lugar na lista de argumentos, por exemplo, na chamada
qc.cx(0, 1), o qubit 0 será o controle e o qubit 1 será o alvo. Da mesma forma, na chamada manualqc.append(CXGate(), [0, 1]), o qubit 0 será o controle e o qubit 1 será o alvo.
Vamos ilustrar essas convenções com alguns exemplos.
Rotulagem de bits
Faça o circuito:
from qiskit import QuantumCircuit
qc = QuantumCircuit(5, 5)
qc.x(0)
qc.x(1)
qc.x(4)
qc.measure(range(5), range(5))Isso inverte os estados dos qubits 0, 1 e 4 de para e, em seguida, mede todos os qubits no clbit correspondente usando a base computacional ( ). Se simulado sem ruído, a saída da cadeia de bits desse circuito será todas as vezes; os qubits 0, 1 e 4 são invertidos e os valores "um" na cadeia de bits estão no zerésimo, primeiro e quarto dígitos a partir da direita.
No Qiskit, escreveríamos o estado do qubit imediatamente antes da medição na abreviação ket-notation como . Observe que o rótulo ket corresponde à cadeia de bits clássica e tem o valor binário numérico de 19.
Se desenharmos esse circuito, veremos que o Qiskit coloca o zerésimo qubit na parte superior do desenho do circuito:
qc.draw("mpl")
Representações matriciais
Os vetores de estado são definidos de acordo com a convenção de que, para um sistema de dois níveis, a relação entre a representação abstrata e a representação matricial é tal que
em que e são números complexos. Armazenamos o vetor de estado como um 1D Numpy ndarray com dados sv = [alpha, beta], ou seja sv[0] == alpha e sv[1] == beta; observe que os índices no vetor de estado correspondem aos rótulos dos kits.
Construímos o produto tensorial de dois estados de qubit na álgebra matricial usando o produto de Kronecker, com o qubit 0 à direita e o qubit 1 à esquerda, de modo que o estado da base (onde é a interpretação inteira da cadeia de bits) tem seu termo diferente de zero no vetor de estado sv em sv[x]:
import numpy
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
state_0 = [1, 0] # defined representation of |0>
state_1 = [0, 1] # defined representation of |1>
# Circuit that creates basis state |10011>, where
# binary 10011 has the decimal value 19.
qc = QuantumCircuit(5)
qc.x(0)
qc.x(1)
qc.x(4)
qiskit_sv = Statevector(qc)
# List index 'n' corresponds to qubit 'n'.
individual_states = [
state_1,
state_1,
state_0,
state_0,
state_1,
]
# Start from a scalar.
manual_sv = [1]
for qubit_state in individual_states:
# Each new qubit goes "on the left".
manual_sv = numpy.kron(qubit_state, manual_sv)
# Now `qiskit_sv` and `manual_sv` are the same, and:
assert manual_sv[19] == 1
assert qiskit_sv[19] == 1Isso se aplica à representação matricial dos operadores e se une às convenções sobre ordens de bits para operadores controlados. Por exemplo, a forma matricial de CXGate é:
import numpy
from qiskit.circuit.library import CXGate
numpy.array(CXGate())Isso pode ser diferente de outras representações matriciais que você já viu em , mas lembre-se de que a escolha da representação matricial é convencional, e essa forma corresponde às convenções do Qiskit de que os qubits de controle vêm em primeiro lugar e o produto tensorial é representado de forma que haja uma correspondência entre o índice da "amplitude de um" e o valor da cadeia de bits de um estado.
No caso de vários controles para uma porta, como para CCXGateo argumento ctrl_state é interpretado como o valor da cadeia de bits dos qubits de controle, usando as mesmas convenções de rotulagem baseadas em zero. Por exemplo, considerando que o ctrl_state padrão é o bitstring all-ones, podemos ver que a forma de matriz de CCXGate com ctrl_state = 1 é a mesma que se usássemos o estado de controle totalmente individual CCXGatemas invertêssemos o valor do qubit de controle indexado mais alto na entrada e existisse no portão:
from qiskit import QuantumCircuit
from qiskit.quantum_info import Operator
# Build the natural representation of `CCX` with the
# control qubits being `[0, 1]`, relative to the
# bitstring state "01", such that qubit 0 must be in |1>
# and qubit 1 must be in |0>. The target qubit is 2.
ccx_natural = QuantumCircuit(3)
ccx_natural.ccx(0, 1, 2, ctrl_state=1)
# Build the same circuit in terms of the all-ones CCX.
# Note that we flip _qubit 1_, because that's the one
# that differs from the all-ones state.
ccx_relative = QuantumCircuit(3)
ccx_relative.x(1)
ccx_relative.ccx(0, 1, 2)
ccx_relative.x(1)
assert Operator(ccx_relative) == Operator(ccx_natural)Em ambos os casos, a forma matricial de CCXGate em ctrl_state = 1 é: