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":
Dentro de um circuito, cada operação completa CircuitInstruction é composta por um Operation (que pode ser um Instruction, um Gate, ou alguma outra subclasse) e pelos operandos qubit e clbit. As principais classes base aqui são:
-
CircuitInstruction, uma operação e seus operandos -
InstructionSet, um identificador temporário para uma fatia de dados do circuito -
Operation, qualquer objeto matemático abstrato ou instrução de hardware-
AnnotatedOperation, uma subclasse com modificadores abstratos aplicados
-
A subclasse concreta mais comum da interface mínima e abstrata Operation é a Instruction. Embora Operation possa incluir objetos matemáticos abstratos, um Instruction é algo que, em teoria, poderia ser executado diretamente no hardware. Essa, por sua vez, é subclassada por Gate e ControlledGate , que acrescentam ainda mais unitaridade e semântica controlada:
Instruction, representando uma instrução baseada em hardwareGate, representando uma instrução de hardware que é unitáriaControlledGate, representando 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 -
ControlFlowOp, que possui subclasses específicas:BreakLoopOp, para sair do loop de contenção mais próximoContinueLoopOp, para passar imediatamente para a próxima iteração do loop contidoBoxOp, um simples conjunto de instruçõesForLoopOp, para percorrer um intervalo fixo de valoresIfElseOp, para entrar condicionalmente em um dos dois subcircuitosSwitchCaseOp, para entrar condicionalmente em um dos muitos subcircuitosWhileLoopOp, para repetir um subcircuito até que uma condição seja refutada.
Certas instruções podem ser “anotadas” com metadados, que normalmente se destinam a ser utilizados por uma etapa do compilador, seja localmente, seja em um processamento posterior no backend. Atualmente, isso está limitado a BoxOp. Essas anotações são representadas por subclasses personalizadas de Annotation, e 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 das expressões em tempo de compilaçãoParameterExpression, um cálculo simbólico sobre parâmetrosParameterVector, uma coleção prática com diversosParametersParameterVectorElement, uma subclasse deParameterutilizada 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:
EquivalenceLibrary, um banco de dados de relações de decomposição entre portas lógicas e circuitosBasisTranslatorSessionEquivalenceLibrary, uma instância mutável daEquivalenceLibraryqual é usada por padrão pelo compilador.
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, os bits clássicos, os parâmetros de tempo de compilação, as variáveis em tempo real e outras informações de rastreamento sobre os dados sobre os quais atua e como é parametrizado. Em seguida, ela contém uma sequência de CircuitInstructions, que incluem a operação específica (porta lógica, 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 QuantumCircuit.data sequência é o objeto “contexto de instrução”, CircuitInstruction. Trata-se, essencialmente, 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 ela atua (o qubits e clbitso ).
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
CircuitInstruction(operação[, qubits, clbits] ) | Uma única instrução em um QuantumCircuité composta pelo endereço operation e por vários operandos. |
Do ponto de vista da programação, essa classe é, na verdade, implementada em Rust e é um identificador criado para acessar dados internos no espaço do Rust. Alterações feitas em instâncias desta 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 interface foi projetada para armazenar e construir circuitos, enquanto o transpiler, suas passagens e sua representação intermediária DAGCircuit são onde você deve procurar uma interface para alterar os circuitos.
Os QuantumCircuit métodos que adicionam instruções aos circuitos (como append(), e todos os métodos auxiliares de portas padrão) retornam um InstructionSet, que é um identificador para vários CircuitInstructions simultaneamente.
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
InstructionSet(*[, solicitante_do_recurso] ) | Coleção de instruções e seus contextos. |
Atualmente, isso InstructionSet já é pouco utilizado no Qiskit. Ele oferece um conjunto muito reduzido de métodos para realizar modificações pós-acrescentamento nas instruções (que serão propagadas para o circuito), mas o uso desses métodos agora não é recomendado, e você deve utilizar as alternativas indicadas nesses métodos.
Operações, instruções e portas
Dentro de um CircuitInstruction, a interface mínima que qualquer operação deve cumprir é Operation. Essa é uma visão de nível muito alto e só pode ser utilizada 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 a circuitos abstratos.
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
Operation() | Interface de operação quântica. |
InstructionA maioria das operações, incluindo todas as operações em circuitos físicos, são exemplos da noção definida de forma mais concreta. Isso se refere a qualquer instrução que alguma QPU possa ser capaz de executar nativamente, como, por exemplo, Measure. Instruction não precisa ser unitária (da mesma forma que Measure não é); uma instrução é especificamente unitária se for uma Gate.
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
Instruction(nome, num_qubits, num_clbits, parâmetros) | Instrução quântica genérica. |
InstructionAs funções s podem ser praticamente arbitrárias, desde que atuem apenas sobre Qubits e Clbits e sejam parametrizadas por seu params; elas não devem tentar “abranger” registros de circuitos externos nem utilizar parâmetros ocultos em seu interior. InstructionOs s podem ser relacionados a outros circuitos para fornecer uma decomposição por meio de seu Instruction.definition atributo, o que proporciona uma decomposição local e pontual. Isso pode ser feito em qualquer conjunto de operações que for mais conveniente para você, desde que as definições de todas as portas incluídas tenham alguma ordem topológica; ou seja, você não pode usar uma porta em uma definição se a própria definição dela depender da porta pai. Se o Instruction dever ser considerado totalmente opaco para os otimizadores, seu definition pode ser None. Consulte “Criação de instruções personalizadas” para obter mais detalhes.
O domínio params de uma instrução pode, tecnicamente, ser arbitrário, mas, em geral, deve-se procurar utilizar parametrizações em termos de números reais, sempre que possível. O próprio Qiskit quebra essa regra em vários pontos, e você encontrará todos os tipos de tipos incomuns nos Instruction.params campos, mas esses são uma fonte incômoda de bugs, pois muitas vezes implicam na necessidade de tratamentos especiais que levem em conta o tipo. Se sua instrução for parametrizada em termos de ângulos, você poderá utilizar com segurança a parametrização em tempo de compilação nela, e ela se integrará bem com QuantumCircuit.assign_parameters().
Embora Instruction não seja necessariamente unitário, sua subclasse Gate implica unitaridade e adiciona to_matrix() os métodos e control() a todos os métodos herdados de Instruction.
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
Gate(nome, num_qubits, parâmetros[, rótulo] ) | Portão unitário. |
Gate herda todos os métodos para Instruction e todas as mesmas considerações sobre seus params campos e definition , exceto, é claro, que Gates não pode atuar sobre nenhum recurso clássico.
definitionGate As instâncias podem (e devem) ter uma base, mas também é possível especificar várias decomposições diferentes em bases distintas usando um EquivalenceLibrary.
GateNo que diz respeito à criação de subclasses, o Qiskit também possui uma classe especial ControlledGate . Essa classe é a base de muitas portas da biblioteca padrão que são controladas (como CXGate), e é lá que você provavelmente a encontrará:
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
ControlledGate(nome, num_qubits, parâmetros[,...] ) | Portão unitário controlado. |
Cada um dos elementos Instruction, Gate e ControlledGate possui um tipo singleton correspondente, construído por meio do mecanismo descrito em qiskit.circuit.singleton. A documentação no nível do módulo contém todos os detalhes, além de descrições de SingletonInstruction, SingletonGate e SingletonControlledGate. Do ponto de vista do usuário, pouco muda dependendo de a classe base ser ou não um singleton; a intenção sempre é que você chame to_mutable() primeiro caso precise obter uma cópia própria e segura para modificaçã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 type seja desaconselhada, se você realmente precisar dela, a maneira confiável de descobrir o tipo “base” de uma instrução potencialmente singleton é usar base_class.
ControlledGate utiliza os mesmos mecanismos que a subclassificação de portas para definir uma síntese fixa e preguiçosa para si mesma. É claro que isso não leva em conta o hardware e é mais difícil de integrar às rotinas de síntese do compilador, mas funciona melhor como uma implementação concreta Instruction que poderia, potencialmente, ser executada nativamente no hardware. base_opNos casos em que a síntese e a otimização abstrata são mais importantes, o Qiskit oferece uma classe composível chamada AnnotatedOperation, que rastreia “modificadores de porta” (dos quais ControlModifier é um) para aplicar ao.
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
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, de certa forma, são “especiais”. Esses casos geralmente exigem um tratamento especial no código do circuito, no transpiler ou nos modelos de hardware. Em geral, essas são instruções com as quais você provavelmente já está 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.
É possível instruir o hardware a aplicar um período de inatividade em tempo real a um determinado qubit. Um circuito programado (ver qiskit.transpiler) incluirá explicitamente todos os tempos de inatividade dos qubits em termos desse Delay. BoxOp também pode ter uma duração explícita associada, em seu BoxOp.duration campo.
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 uma análise em tempo real Expr pode ser utilizado diretamente em determinados casos. Atualmente, isso se limita às condições de IfElseOp e WhileLoopOp, e ao alvo de SwitchCaseOp. O resultado também pode ser armazenado em um local de armazenamento clássico tipado, utilizando a Store instrução (ou seu QuantumCircuit.store() construtor), apoiada por um 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 projetadas para serem resolvidas em tempo de compilação. Elas se caracterizam pelo uso das Parameter classes e ParameterExpression .
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
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 em relação às expr.Var variáveis utilizadas 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 comum sobre os números reais contínuos (e, em casos limitados, sobre os números complexos). Em contrapartida, Var é um identificador de uma variável armazenada em um computador clássico, como um valor de ponto flutuante ou um inteiro de largura fixa, que são sempre discretos.
Em outras palavras, você pode esperar ParameterExpression realizar simplificações simbólicas que sejam válidas na matemática, como simplificar . Tal simplificação não é válida na aritmética de ponto flutuante e expr.Expr não será realizada.
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 vários parâmetros que estejam relacionados entre si. ParameterVectorPara facilitar isso (e para evitar que você precise inventar muitos nomes), você pode usar o construtor de conveniência. ParameterVectorElementOs elementos do vetor são todos instâncias válidas Parameter de uma subclasse especial.
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
ParameterVector(nome[, comprimento, uuid] ) | Um contêiner que contém muitos objetos relacionados Parameter . |
ParameterVectorElement(vetor, índice[, uuid] ) | Um elemento de um ParameterVector. |
Controle de fluxo em circuitos
InstructionNo QuantumCircuit, o fluxo de controle clássico é representado por s específicos, que são subclasses de ControlFlowOp.
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
ControlFlowOp(nome, num_qubits, num_clbits,...) | Classe abstrata para encapsular todas as operações de fluxo de controle. |
Por conveniência, há uma frozenset instância que contém os 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'>Todas essas operações de fluxo de controle (IfElseOp, WhileLoopOp, SwitchCaseOp, ForLoopOp, e BoxOp) possuem 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.
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
IfElseOp(condição, corpo_verdadeiro[, corpo_falso,...] ) | 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(conjunto de índices, parâmetro_de_loop, corpo[,...] ) | 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 reconhece 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.
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
BreakLoopOp(num_qubits, num_clbits[, label] ) | Uma operação de circuito que, quando encontrada, salta para o final do loop adjacente mais próximo. |
ContinueLoopOp(num_qubits, num_clbits[, label] ) | 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 ControlFlowOp as subclasses também Instruction são subclasses, isso significa que a forma como são armazenadas nas CircuitInstruction instâncias faz com que sejam “aplicadas” a uma sequência de qubits e clbits em seus qubits atributos e clbits . Isso pode levar a problemas sutis de coerência de dados: os Qubit objetos e Clbit utilizados dentro dos blocos de subcircuitos das operações de fluxo de controle não serão necessariamente idênticos aos objetos correspondentes no CircuitInstruction. Qualquer código que utilize operações de fluxo de controle no Qiskit precisa levar isso em consideração; dentro de um subcircuito, você deve tratar subcircuit.qubits[i] como se fosse realmente outer_instruction.qubits[i], e assim por diante. Você pode criar uma tabela de consulta simples para isso da seguinte maneira:
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 subcircuitos em um ControlFlowOp devem conter o mesmo número de Qubits e Clbits, referindo-se aos mesmos bits externos na mesma ordem, de modo que o zip loop apresentado no bloco de código acima funcione. Os objetos do circuito Bit interno não precisam ser, literalmente, os mesmos objetos. {x: x}Ao usar a interface do construtor de fluxo de controle (que, vale ressaltar, é altamente recomendada aos usuários), os construtores farão com 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, mas, se você estiver utilizando os blocos, deve estar preparado para o caso em que o mapeamento for necessário.
Qualquer ClassicalRegisters utilizado em um subcircuito de fluxo de controle também deve estar presente em todos os blocos que o contêm ( ou seja, em todas as operações de fluxo de controle que o contêm e no circuito mais externo), e todos os blocos nesse mesmo ControlFlowOp circuito precisam conter os mesmos registros. Mais uma vez, a interface do construtor garantirá que isso aconteça (ou gerará um erro antecipado caso não seja 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.
Certas instruções de circuito podem ser “anotadas” com anotações locais à instrução. Conforme o Qiskit 2.1.0, isso está limitado a BoxOp. Todas as anotações são subclasses de um único objeto que define a interface base, mas, normalmente, representam análises e comandos totalmente personalizados.
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
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 transpiler, mas as ferramentas para investigar comutações estão disponíveis no CommutationChecker.
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
CommutationChecker([...]) | Verifique as comutações de duas operações. |
Agendamento de resolução atrasada
Normalmente, o resultado do compilador do Qiskit não pode ser executado diretamente em uma QPU. Primeiro, é provável que passe por algum compilador de nível de pulso específico do fornecedor, que converte as portas lógicas e as medições em sinais para a eletrônica de controle da QPU. Embora o Qiskit Target possa representar algumas das restrições de tempo que esses pulsos terão, geralmente essa representação não é totalmente completa. Isso é especialmente verdadeiro quando se trata de circuitos dinâmicos (operações de feed-forward); os atrasos induzidos pelos componentes clássicos costumam depender de detalhes de baixo nível da pós-otimização dos compiladores de backend 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 simples e pontuais que serão adicionadas a um circuito, e os blocos estiverem sendo usados apenas para fins de visualização ou agrupamento, a maneira mais fácil de criar uma instrução ou porta personalizada é simplesmente definir sua definição como um QuantumCircuit, e então usar seu to_instruction() método ou to_gate() conforme for apropriado. Os resultados podem ser fornecidos diretamente ao QuantumCircuit.append() circuito maior. Gate Esses métodos criarão instâncias básicas Instruction cujo definition atributo é o circuito fornecido, o que significa que ele estará automaticamente acessível ao transpiler 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
InstructionAs classes base, Gate e ControlledGate foram todas projetadas para que a criação de subclasses seja segura e possuem pontos de integração para que as subclasses possam implementá-las. Se o seu gate personalizado não tiver parâmetros nem estado, talvez você também queira herdar da classe singleton correspondente em qiskit.circuit.singleton, como, por exemplo, SingletonGate. Você deve consultar a documentação em qiskit.circuit.singleton para conhecer métodos adicionais e pontos de integração do mecanismo de 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 estado adicional na classe, mas suas subclasses se integrarão de forma mais confiável com o restante da estrutura do Qiskit se você depender apenas de seus Instruction.params, e esses parâmetros forem exclusivamente â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 oculto _definition que sustenta o método público definition .
_define
Instruction._define()
InstructionPreencha o campo “cached” _definition deste.
As subclasses devem implementar esse método para permitir a construção diferida de seu atributo público definition . Uma subclasse pode usar seu params no momento da chamada. O método deve preencher _definition com um QuantumCircuit e não retornar um valor.
Nas subclasses de ControlledGate, o _define() método deve implementar a decomposição apenas para o estado de controle em que todos os bits são 1. O ControlledGate.definition mecanismo irá ajustar isso para se adequar ao estado real do controle.
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 sobrescrever o método de instância do protocolo de matriz do Numpy, __array__. Isso é usado por Gate.to_matrix()e possui a assinatura:
__array__
object.__array__(dtype=None, copy=None)
Retorna um array do Numpy que representa a porta lógica. Isso pode utilizar o campo da params porta e pode presumir que se trata de valores numéricos (supondo que a subclasse espere isso) e não de parâmetros de tempo de compilação.
Para maior eficiência, a matriz retornada deve ter como padrão um dtype de complex.
Se sua subclasse personalizada tiver representações naturais de suas formas controladas ou inversas, talvez você também queira sobrescrever os inverse() métodos e control() .
Como exemplo de definição de um portão “ ” personalizado; ou seja, uma rotação de ângulo único em torno do eixo . RZXGateIsso significa, basicamente, que se os qubits estivessem na posição inversa, então vamos escrever nossa definição com base nisso. GateSomos paramétricos, portanto não podemos ser um singleton, mas somos unitários, portanto deveríamos ser um:
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 básica em termos de RZXGate, mas, para permitir decomposições mais rápidas em uma variedade de bases, talvez seja interessante adicionar mais algumas equivalências a SessionEquivalenceLibrary. Observe que a BasisTranslator pesquisa de tradução analisará todas as equivalências possíveis em todas as profundidades possíveis; portanto, fornecer uma equivalência em termos de (digamos) XGate tornará automaticamente disponíveis também as decomposições em termos de RXGate .
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)A partir de então, enquanto durar a sessão do interpretador Python, tradutores como BasisTranslator encontrarão nossa nova definição em suas pesquisas.
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 ter mais acesso e controle sobre as etapas das execuções que serão realizadas, use generate_preset_pass_manager() para criar um StagedPassManager primeiro, que você poderá modificar posteriormente.
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, é possível simular os efeitos no estado do passando-o QuantumCircuit diretamente para o Statevector construtor padrão. Da mesma forma, é possível obter uma matriz unitária que represente o circuito como um operador, passando-o ao construtor Operator padrão. Se você tiver um circuito físico, talvez seja melhor passá-lo ao Operator.from_circuit() método para aplicar as transformações do QuantumCircuit.layout a fim de remapeá-lo de volta ao espaço “abstrato” de qubits.
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 backends em formato bruto que ele pode retornar para você.StatevectorSimulatorpara um wrapper semelhante a um backend em torno deStatevector- O
qiskit_aerpara recursos de simulação completos e de alto desempenho. StatevectorSamplereStatevectorEstimatorpara implementações de referência das primitivas do Qiskit, baseadas em simulador.
Definindo relações de equivalência
Uma tarefa comum ao mapear circuitos abstratos para hardware físico e otimizar o resultado é encontrar relações de equivalência que mapeiem uma porta lógica para um conjunto de bases diferente. O Qiskit armazena essas informações em uma classe de banco de dados chamada EquivalenceLibrary.
Coluna “ 1 ” | Coluna “ 2 ” |
|---|---|
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
Um EquivalenceLibrary que armazena todas as relações entre os portões padrão integrados ao Qiskit. SessionEquivalenceLibraryVocê não deve alterar este objeto; em vez disso, crie o seu próprio EquivalenceLibrary usando este como modelo baseou modifique o estado global.
O Qiskit também define um objeto de estado global compartilhado, SessionEquivalenceLibrary, que representa as equivalências padrão utilizadas em vários pontos do Qiskit, principalmente na BasisTranslator etapa do transpiler. Sinta-se à vontade para adicionar suas próprias equivalências a isso usando o add_equivalence() método, e elas serão automaticamente reconhecidas pelas instâncias padrão do BasisTranslator.
qiskit.circuit.SessionEquivalenceLibrary
A instância padrão de EquivalenceLibrary, que será utilizada pela maioria dos objetos do Qiskit caso nenhuma biblioteca seja especificada manualmente. Fique à vontade para adicionar equivalências a isso usando add_equivalence(). Ele herda todas as regras integradas 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 for que esta função seja executada após transpile() ou PassManager.run() , o argumento opcional target pode ser utilizado para que as portas de Pauli de 1 qubit inseridas sejam sintetizadas de forma a serem compatíveis com o valor fornecido Target , de modo que o(s) circuito(s) de saída continuem compatíveis.
Parâmetros
- circuit (QuantumCircuit) – O circuito para girar
- twirling_gate (None | str |Gate |list[str] | list[Gate]) – O parâmetro “gate to twirl” tem como valor padrão “None”, o que significa que todos os gates padrão serão girados:
CXGate,CZGate,ECRGate, eiSwapGate. Se fornecido, pode ser um único gate ou uma lista de gates, seja na forma de um objeto gate ou de seu nome como string. Atualmente, apenas os nomes “cx”, “cz”, “ecr” e “iswap” são compatíveis. Se um objeto de gate for fornecido fora dos gates padrão, ele deverá ter uma matriz definida em seuto_matrixmétodo para que o gate possa, eventualmente, ser girado. Se não for possível calcular uma configuração válida de rotação, esse portão específico será ignorado silenciosamente e não sofrerá rotação. - 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
Targetinstância a ser usada para executar a decomposição de um único qubit como parte do “Pauli twirling”, a fim de otimizar e mapear as portas de Pauli adicionadas ao circuito para o alvo 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 reflete na representação matricial dos operadores e se alinha às convenções sobre a ordem dos 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 múltiplos controles para um portão, como por exemplo para CCXGate, o ctrl_state argumento é interpretado como o valor da sequência de bits dos qubits de controle, utilizando as mesmas convenções de numeração com base em zero. CCXGatePor exemplo, considerando que o padrão ctrl_state é a sequência de bits composta inteiramente por uns, podemos observar que a forma matricial de CCXGate com ctrl_state = 1 é a mesma que se tivéssemos considerado o estado de controle composto inteiramente por uns, mas invertido o valor do qubit de controle com índice mais alto na entrada e na saída da porta:
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 é: