Modelo de circuito cuántico
qiskit.circuit
El elemento fundamental de la informática cuántica es el circuito cuántico. Se trata de una rutina computacional que puede ejecutarse, disparo a disparo, en una unidad de procesamiento cuántico (QPU). Un circuito actuará sobre una cantidad predefinida de datos cuánticos (en Qiskit, sólo admitimos qubits directamente) con operaciones unitarias (puertas), mediciones y reinicios. Además, un circuito cuántico puede contener operaciones sobre datos clásicos, incluidos cálculos en tiempo real y construcciones de flujo de control, que son ejecutadas por los controladores de la QPU.
Si lo desea, puede saltarse el material introductorio y pasar directamente a:
- el resumen API de todo el módulo del circuito
- el debate detallado sobre cómo se representan los circuitos
- el núcleo
QuantumCircuitpara saber cómo construir y consultar circuitos - información sobre la construcción instrucciones personalizadas
- formas de trabajar con objetos a nivel de circuito
- debate sobre las convenciones de Qiskit para circuitos, matrices y etiquetado de estados
Los circuitos se encuentran en un nivel bajo de abstracción cuando se construyen programas cuánticos. Son el constructo que se utiliza para llegar a niveles superiores de abstracción, como las primitivas de la computación cuántica, que acumulan datos de muchos disparos de ejecución de circuitos cuánticos, junto con técnicas avanzadas de mitigación de errores y optimizaciones de medición, en datos clásicos bien tipificados y estadísticas de error.
En Qiskit, los circuitos pueden definirse en uno de dos regímenes :
- un circuito abstracto, que se define en términos de qubits virtuales y operaciones arbitrarias de alto nivel, como algoritmos encapsulados y puertas definidas por el usuario.
- un circuito físico, que se define en términos de los qubits hardware de un backend concreto, y contiene sólo operaciones que este backend soporta de forma nativa. Este concepto también se conoce como circuito ISA.
La conversión de un circuito abstracto a un circuito físico se realiza utilizando el paquete de transpilación de Qiskit, cuyo punto de acceso de nivel superior es transpile(). Si defines un circuito, donde pretendes que los índices qubit se refieran a qubits físicos, puedes usar QuantumCircuit.ensure_physical() para reescribir los metadatos del circuito para asegurar que Qiskit reconoce el circuito como un circuito físico, aunque a diferencia de la transpilación, esto no obliga a que las puertas base y las restricciones de acoplamiento de hardware sean respetadas.
En Qiskit, un circuito cuántico está representado por la clase QuantumCircuit clase. A continuación se muestra un ejemplo de circuito cuántico que realiza un estado Greenberger-Horne-Zeilinger (GHZ) de tres 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')
Conceptos y definiciones de circuitos
Hay mucha terminología especializada en torno a los circuitos cuánticos. Gran parte de esto es común en la literatura sobre computación cuántica, mientras que otra parte es más específica de los paquetes de software cuántico, y una pequeña cantidad específica de Qiskit. Se trata de una lista alfabética de algunos de los conceptos importantes como referencia rápida, pero no entra en detalles de los conceptos fundamentales. Considere la posibilidad de utilizar la plataforma IBM Quantum Learning si desea empezar desde el principio.
circuito abstracto
Un circuito definido en términos de operaciones matemáticas abstractas y qubits virtuales. Así es como normalmente se piensa en los algoritmos cuánticos: un circuito abstracto puede estar formado por operaciones unitarias completamente arbitrarias, mediciones y potencialmente computación clásica en tiempo real, sin restricciones sobre qué qubits pueden interactuar entre sí.
Convierte un circuito abstracto en un circuito físico utilizando el paquete de transpilación de Qiskit.
ancilla qubit
Un qubit extra que se utiliza para ayudar a implementar operaciones en otros qubits, pero cuyo estado final no es importante para el programa.
circuit
Rutina computacional que define una única ejecución a realizar en una QPU. Puede tratarse de un circuito abstracto o de un circuito físico.
clbit
Abreviatura específica de Qiskit que significa un único bit clásico de datos.
puerta
Operación unitaria sobre uno o varios qubits.
qubit de hardware
La representación de un único qubit en una QPU concreta. Un qubit físico está respaldado por un sistema físico cuántico-mecánico, como los circuitos superconductores; a diferencia de un qubit virtual, tiene restricciones de acoplamiento particulares y sólo pueden aplicarse ciertas puertas a determinados grupos de qubits físicos.
Qiskit no distingue entre qubits lógicos y qubits físicos individuales cuando habla de qubits de hardware. Una QPU puede implementar sus qubits hardware como qubits lógicos, donde cada qubit hardware comprende muchos qubits físicos que son controlados y corregidos de errores de forma opaca a Qiskit por la electrónica de control. Lo más probable, para aplicaciones a corto plazo, es que una QPU exponga directamente sus qubits físicos como qubits de hardware para que Qiskit pueda razonar sobre ellos.
Tanto los qubits físicos como los lógicos tendrán restricciones de acoplamiento entre ellos, sólo permitirán ciertas operaciones cuánticas sobre ellos y tendrán problemas de programación entre ellos. Qiskit abstrae estas preocupaciones en el concepto de qubits de hardware. En los primeros días de la corrección cuántica de errores, determinados backends pueden permitirte acceder a sus recursos de qubits como qubits lógicos de alto nivel o como qubits físicos de bajo nivel a través de Qiskit.
arquitectura del conjunto de instrucciones (ISA)
El modelo abstracto de qué operaciones están disponibles en qué conjuntos de qubits de hardware en una QPU concreta. Por ejemplo, una QPU puede permitir las operaciones y en todos los qubits hardware individuales, y las operaciones en determinados pares de qubits hardware.
qubit lógico
Una colección de varios qubits físicos controlados conjuntamente por una QPU (desde la perspectiva del usuario) para aplicar la corrección cuántica de errores en tiempo real. Un qubit lógico es un tipo de qubit de hardware para Qiskit.
measurement
El acto de extraer un bit clásico de un dato a partir de un único estado qubit. Se trata de una operación irreversible, y normalmente destruye el entrelazamiento y la coherencia de fase entre el qubit objetivo y el resto del sistema.
circuito físico
Un circuito definido en términos de qubits de hardware y solo las operaciones cuánticas disponibles en la ISA de una QPU particular. Los circuitos físicos están vinculados a una arquitectura QPU concreta y no funcionarán en otras arquitecturas incompatibles. También se le conoce como circuito ISA.
Normalmente se obtiene un circuito físico utilizando las rutinas de transpilación de Qiskit en un circuito abstracto que has construido.
qubit físico
Un sistema cuántico de dos niveles controlable. Se trata literalmente de un qubit "físico", como un transmón o el estado electrónico de un ion atrapado. Una QPU puede exponer esto directamente como su qubit hardware, o combinar varios qubits físicos en un qubit lógico.
unidad de procesamiento cuántico (QPU)
Análoga a una CPU en informática clásica o una GPU en procesamiento gráfico, una QPU es el hardware que ejecuta operaciones cuánticas sobre datos cuánticos. Siempre se puede esperar que una QPU que utilice el modelo de circuito de computación sea capaz de realizar algún conjunto de puertas y operaciones de medición. Dependiendo de la tecnología de que se trate, también pueden realizar algunos cálculos clásicos en tiempo real, como el flujo de control clásico y cálculos de bit a bit sobre datos clásicos.
Qubit
La unidad básica de la información cuántica.
cálculo clásico en tiempo real
Cualquier cómputo clásico que puede ocurrir dentro de la ejecución de un solo disparo de un circuito, donde los resultados del cómputo clásico pueden afectar a la ejecución posterior del circuito. La cantidad de cálculo clásico en tiempo real disponible con determinadas QPU variará significativamente en función de muchos factores, como la electrónica de control y la tecnología de qubits utilizada. Consulte la documentación de su proveedor de hardware para obtener más información al respecto.
operación unitaria
Una operación reversible sobre un estado cuántico. Todas las puertas cuánticas son operaciones unitarias (por definición).
qubit virtual
Un qubit matemático abstracto utilizado para construir un circuito abstracto. Suponemos que todas las puertas cuánticas son válidas en todos los qubits virtuales y que todos los qubits virtuales están siempre conectados a cualquier otro qubit virtual.
Al asignar a hardware, los qubits virtuales deben asignarse a qubits de hardware. Esta correspondencia no tiene por qué ser unívoca. Normalmente, será necesario intercambiar un qubit virtual de un qubit hardware a otro en el transcurso de la ejecución de un circuito para satisfacer las restricciones de acoplamiento de la QPU subyacente. No es estrictamente necesario que todos los qubits virtuales utilizados en un circuito se asignen a un qubit físico en cualquier punto dado de un circuito físico; podría ser que un qubit virtual se midiera (colapsando su estado) y luego nunca se volviera a utilizar, por lo que un nuevo qubit virtual podría ocupar su lugar. El paquete de transpilación de Qiskit se encarga de evaluar estas condiciones para asignar un circuito virtual a un circuito físico.
Descripción general de la API de qiskit.circuit
Todos estos objetos se describen con más detalle y en su contexto más amplio en las secciones siguientes. Esta sección proporciona una visión general de los elementos de la API aquí documentados.
La clase principal es QuantumCircuitque tiene su propia página de documentación, incluyendo una sección en profundidad sobre la construcción de circuitos. Los datos cuánticos y los datos clásicos más simples se representan mediante "bits" y "registros":
Dentro de un circuito, cada CircuitInstruction está formado por un Operation (que puede ser un Instruction, a Gateo alguna otra subclase) y los operandos qubit y clbit. Las clases básicas son:
-
CircuitInstructionuna operación y sus operandos -
InstructionSetun manejador temporal a una porción de datos del circuito -
Operationcualquier objeto matemático abstracto o instrucción de hardware-
AnnotatedOperationuna subclase con modificadores abstractos aplicados
-
La subclase concreta más común de la interfaz abstracta mínima Operation es la interfaz Instruction. Mientras que Operation puede incluir objetos matemáticos abstractos, un Instruction es algo que podría ejecutarse directamente en el hardware. Éste, a su vez, es subclase de Gate y ControlledGate que añaden además la unitaridad y la semántica controlada:
Instructionque representa una instrucción basada en hardwareGateque representa una instrucción hardware unitariaControlledGateque representa una puerta con estructura de control.
Qiskit incluye una amplia biblioteca de compuertas y circuitos estándar, que está documentada en qiskit.circuit.library. Muchos de ellos se declaran como Python -object singletons. La maquinaria para ello se describe en detalle en qiskit.circuit.singletoncuyas clases principales son cada una una forma singleton de la jerarquía estándar de puertas de instrucción:
Algunas instrucciones son especialmente especiales porque afectan al flujo de control o al flujo de datos del circuito. Los de más alto nivel son:
-
Barrier, para marcar las partes del circuito que deben optimizarse de forma independiente -
Delaypara insertar un periodo de espera en tiempo real -
Resetpara restablecer irreversiblemente un qubit al estado -
Storepara escribir una expresión clásica en tiempo real en una ubicación de almacenamiento -
ControlFlowOpque tiene subclases específicas:BreakLoopOppara salir del bucle de contención más próximoContinueLoopOppara pasar inmediatamente a la siguiente iteración del bucle contenedorBoxOpuna simple agrupación de instruccionesForLoopOppara recorrer un intervalo fijo de valoresIfElseOppara entrar condicionalmente en uno de los dos subcircuitosSwitchCaseOppara entrar condicionalmente en uno de los muchos subcircuitosWhileLoopOppara repetir un subcircuito hasta que se falsifique una condición.
Ciertas instrucciones pueden ser "anotadas" con metadatos, que normalmente están destinados a ser consumidos por un pase de compilador, ya sea localmente, o en el procesamiento posterior de backend. Actualmente se limita a BoxOp. Estas anotaciones se representan mediante subclases personalizadas de Annotationy hay más información sobre la infraestructura de soporte en qiskit.circuit.annotation.
Los circuitos pueden incluir expresiones clásicas que se evalúan en tiempo real, mientras la QPU ejecuta una única toma del circuito. Se documentan principalmente en la documentación del módulo de qiskit.circuit.classical. Puede que te interesen especialmente las clases base (que no se exponen desde el directorio qiskit.circuit raíz):
Varun lugar de almacenamiento clásico tipificado en un circuitoExpruna expresión evaluada en tiempo realTypeel tipo clásico de una expresión.
Además de esta evaluación de expresiones en tiempo real, limitada por las representaciones hardware clásicas de los datos, Qiskit cuenta con el concepto de parametrización "en tiempo de compilación", que se realiza en álgebra simbólica abstracta. Estos se utilizan normalmente para representar ángulos de puerta en algoritmos de alto nivel que podrían querer realizar derivadas numéricas, pero son una parte más antigua de Qiskit que la evaluación en tiempo real, por lo que todavía se utilizan en algunos lugares para hacer parametrización general. Las principales clases relacionadas son:
Parameterel átomo de las expresiones en tiempo de compilaciónParameterExpressionun cálculo simbólico de los parámetrosParameterVectoruna práctica colección de muchosParametersParameterVectorElementuna subclase deParameterutilizada porParameterVector
El módulo qiskit.circuit también expone algunas clases de cálculo que trabajan con circuitos para ayudar a los flujos de trabajo de compilación. Esto incluye:
EquivalenceLibraryuna base de datos de relaciones de descomposición entre puertas y circuitosSessionEquivalenceLibraryuna instancia mutable deEquivalenceLibraryque utiliza por defecto el compiladorBasisTranslator.
También existen utilidades para generar circuitos aleatorios:
Por último, el módulo de circuito tiene su propia clase de excepción, para indicar cuando las cosas van mal de manera específica del circuito:
Representación de circuitos en Qiskit
La principal clase de usuario para representar circuitos es QuantumCircuit. Puede tratarse de un circuito abstracto o de un circuito físico. Hay mucha más información sobre la clase QuantumCircuit y la multitud de métodos disponibles en la documentación de la clase.
Internamente, un QuantumCircuit contiene los qubits, los bits clásicos, los parámetros en tiempo de compilación, las variables en tiempo real y otra información de seguimiento sobre los datos sobre los que actúa y cómo se parametriza. A continuación, contiene una secuencia de CircuitInstructions, que contienen la operación concreta (puerta, medida, etc.) y sus operandos (los qubits y los bits clásicos).
Bits y registros
Los qubits y los bits clásicos están representados por un tipo base compartido Bit que sólo pretende ser una "etiqueta de tipo"; las clases no tienen otro comportamiento que el de ser objetos inmutables:
Bit
Qubit
class qiskit.circuit.Qubit(register=None, index=None)
Bases: Bit
Un qubit, que puede compararse entre distintos circuitos.
Clbit
class qiskit.circuit.Clbit(register=None, index=None)
Bases: Bit
Un clbit, que puede compararse entre diferentes circuitos.
Qubits y clbits son instanciados por los usuarios sin argumentos, como por ejemplo mediante Qubit(). Los bits se comparan igual si son el mismo objeto Python, o si ambos fueron creados por un registro del mismo nombre y tamaño, y se refieren al mismo índice dentro de ese registro. También existe una etiqueta de tipo especial para qubits "ancilla", pero se utiliza poco en el estado actual de Qiskit:
AncillaQubit
Los bits de una colección del mismo tipo pueden encapsularse en un registro del tipo correspondiente. La funcionalidad base se encuentra en una clase base que no se instancia directamente:
Registrarse
class qiskit.circuit.Register
Bases: object
Implementar un registro genérico.
Esta clase no puede instanciarse directamente. Su único propósito es permitir la comprobación genérica de tipos para ClassicalRegister y QuantumRegister.
Cada uno de los subtipos de bits definidos tiene un registro asociado, que tiene las mismas firmas de constructor, métodos y propiedades que la clase base:
QuantumRegister
class qiskit.circuit.QuantumRegister(size=None, name=None, bits=None)
Bases: Register
Implementar un registro.
ClassicalRegister
class qiskit.circuit.ClassicalRegister(size=None, name=None, bits=None)
Bases: Register
Implementar un registro.
AncillaRegister
class qiskit.circuit.AncillaRegister(size=None, name=None, bits=None)
Bases: QuantumRegister
Implementar un registro ancilla.
Una forma habitual de instanciar varios bits a la vez es crear un registro, como por ejemplo mediante QuantumRegister("my_qreg", 5). Esto tiene la ventaja de que puedes dar un nombre a esa colección de bits, que aparecerá durante las visualizaciones de circuitos (QuantumCircuit.draw()) y las exportaciones a lenguajes de intercambio (véase qasm2 y qasm3). También puedes pasar un nombre y una lista de bits preconstruidos, pero esto crea un "registro de alias", que están muy mal soportados en hardware.
Los circuitos siguen la pista de los registros, pero los registros en sí no aportan apenas diferencias de comportamiento a los circuitos. La única excepción es que ClassicalRegisterse pueden convertir implícitamente en enteros sin signo para utilizarlos en comparaciones condicionales de operaciones de flujo de control.
Los registros clásicos y los bits eran la forma original de representar datos clásicos en Qiskit, y siguen siendo los más soportados actualmente. A largo plazo, el modelo de datos se está moviendo hacia una representación más completa y fuertemente tipada de un rango de datos clásicos (ver Computación clásica en tiempo real ), pero todavía usarás muy comúnmente bits clásicos en el Qiskit actual.
Contextos de instrucción
El tipo escalar de la QuantumCircuit.data secuencia es el objeto "contexto de instrucción", CircuitInstruction. Se trata esencialmente de una clase de datos que contiene una representación de lo que se va a hacer (su operation), y los datos sobre los que actúa (los qubits y clbits).
CircuitInstruction(operación[, qubits, clbits] ) | Una única instrucción en un QuantumCircuitcompuesta por operation y varios operandos. |
Programáticamente, esta clase está realmente implementada en Rust y es un manejador construido para datos internos dentro del espacio de Rust. Las mutaciones de instancias de esta clase no se reflejarán en el circuito. En general, no se pueden modificar directamente los contextos de instrucciones que ya están en el circuito QuantumCircuit interfaz está diseñada para almacenar y construir circuitos, mientras que el transpilador y sus pases, y su representación intermedia, es donde se debe buscar una interfaz para mutar circuitos DAGCircuit es donde debes buscar una interfaz para mutar circuitos.
Los QuantumCircuit que añaden instrucciones a los circuitos (como append()y todos los métodos de ayuda de puerta estándar) devuelven un InstructionSetque es un manejador de varios CircuitInstructions simultáneamente.
InstructionSet(*[, recurso_requester] ) | Recogida de instrucciones y sus contextos. |
Este InstructionSet se utiliza poco en Qiskit. Proporciona un conjunto muy mínimo de métodos para realizar mutaciones post-append en instrucciones (que se propagarán al circuito), pero ahora se desaconsejan y deberías utilizar las alternativas indicadas en esos métodos.
Operaciones, instrucciones y puertas
Dentro de una CircuitInstructionla interfaz mínima que debe cumplir cualquier operación es Operation. Esta es una vista de muy alto nivel, y sólo utilizable para circuitos abstractos. El objetivo principal de tratar las operaciones como Operation es permitir que objetos matemáticos arbitrarios (como quantum_info.Operator) se añadan directamente a los circuitos abstractos.
Operation() | Interfaz de operación cuántica. |
La mayoría de las operaciones, incluidas todas las operaciones sobre circuitos físicos, son instancias de la definición más concreta Instruction. Representa cualquier instrucción que alguna QPU pueda realizar de forma nativa, como por ejemplo Measure. Instruction no tiene por qué ser unitaria (por mucho que Measure no lo es); una instrucción es específicamente unitaria si es un Gate.
Instruction(name, num_qubits, num_clbits, params) | Instrucción cuántica genérica. |
Instructions pueden ser casi arbitrarias, siempre que sólo actúen sobre Qubits y Clbits, y estén parametrizados por su paramss; no deben intentar "cerrar" registros de circuitos externos, ni utilizar parámetros ocultos en su interior. Instructions pueden relacionarse con otros circuitos para proporcionar una descomposición utilizando su atributo Instruction.definition que proporciona una descomposición local y única. Esto puede ser en cualquier conjunto de operaciones base que sea más conveniente para usted, siempre y cuando las definiciones de todas las puertas contenidas tengan algún orden topológico; es decir, no se puede utilizar una puerta en una definición si su propia definición depende de la del padre. Si el Instruction debe considerarse totalmente opaco para los optimizadores, su definition puede ser None. Consulte Crear instrucciones personalizadas para obtener más detalles.
La dirección params de una instrucción puede ser técnicamente arbitrario, pero en general debe intentar ceñirse a parametrizaciones en términos de números reales, siempre que sea posible. Qiskit rompe esta regla en muchos lugares, y encontrarás todo tipo de tipos inusuales en los campos Instruction.params pero estos son una fuente molesta de errores porque a menudo implican la necesidad de un tipo especial. Si su instrucción está parametrizada en términos de ángulos, podrá utilizar de forma fiable la parametrización en tiempo de compilación en ella, y se integrará bien con la instrucción QuantumCircuit.assign_parameters().
Aunque Instruction no es necesariamente unitario, su subclase Gate implica unitariedad, y añade to_matrix() y control() a todos los métodos heredados de Instruction.
Gate(name, num_qubits, params[, label] ) | Puerta unitaria. |
Gate hereda todos los métodos de Instruction y todas las mismas consideraciones sobre su params y definition excepto, por supuesto, que Gates no puede actuar sobre ningún recurso clásico.
Gate pueden (y deben) tener una base definitionpero también se pueden especificar varias descomposiciones diferentes en distintas bases utilizando una base EquivalenceLibrary.
Subclasificación Gateqiskit tiene una clase especial ControlledGate especial. Esta clase es la base de muchas puertas de la biblioteca estándar que se controlan (como CXGate), que es donde es más probable encontrarla:
ControlledGate(name, num_qubits, params[,...] ) | Puerta unitaria controlada. |
Cada uno de Instruction, Gate y ControlledGate tiene un tipo singleton correspondiente, construido utilizando la maquinaria descrita en qiskit.circuit.singleton. La documentación a nivel de módulo contiene todos los detalles, junto con descripciones de SingletonInstruction, SingletonGate y SingletonControlledGate. Desde la perspectiva del usuario, poco cambia en función de si la clase base es un singleton o no; la intención siempre sigue siendo que se debe llamar primero a to_mutable() si necesita obtener una copia de una instrucción que se pueda mutar de forma segura (no puede asumir que una instrucción arbitraria es mutable), y aunque se desaconseja la inspección directa type se desaconseja la inspección directa, si la necesita, la forma fiable de encontrar el tipo "base" de una instrucción potencialmente singleton es utilizar base_class.
ControlledGate utiliza los mismos mecanismos que las puertas de subclase para definir una síntesis fija y perezosa para sí misma. Naturalmente, esto no tiene en cuenta el hardware, y es más difícil de enganchar a las rutinas de síntesis del compilador, pero funciona mejor como un concreto Instruction que podría ejecutarse de forma nativa en el hardware. Para los casos en los que la síntesis y la optimización abstracta son más importantes, Qiskit ofrece una clase componible llamada AnnotatedOperationque rastrea los "modificadores de puerta" (de los cuales ControlModifier es uno de ellos) para aplicarlos al base_op.
AnnotatedOperation(base_op, modificadores) | Operación anotada. |
Los modificadores disponibles para AnnotatedOperation son:
InverseModifier
class qiskit.circuit.InverseModifier
Bases: Modifier
Modificador inverso: especifica que la operación se invierte.
ControlModifier
class qiskit.circuit.ControlModifier(num_ctrl_qubits=0, ctrl_state=None)
Bases: Modifier
Modificador de control: especifica que la operación está controlada por num_ctrl_qubits y tiene estado de control ctrl_state.
Parámetros
PowerModifier
class qiskit.circuit.PowerModifier(power)
Bases: Modifier
Modificador de potencia: especifica que la operación se eleva a la potencia power.
Parámetros
power (float)
Para obtener información sobre cómo crear puertas e instrucciones personalizadas, incluyendo cómo construir objetos únicos y puertas paramétricas reutilizables mediante subclases, consulte Crear instrucciones personalizadas más abajo. La biblioteca de circuitos Qiskit en qiskit.circuit.library contiene muchas puertas y circuitos predefinidos que puedes utilizar.
Instrucciones especiales integradas
Qiskit contiene algunas Instruction clases que son en cierto modo "especiales". Suelen tener un tratamiento especial en el código del circuito, en el transpilador o en los modelos de hardware. En general, se trata de instrucciones con las que quizá ya esté familiarizado.
Las mediciones en Qiskit son de una sola Qubit en una única Clbit. Estos son los dos a los que se aplica la instrucción. Las medidas están en la base computacional.
Medida
class qiskit.circuit.Measure(label=None)
Bases: SingletonInstruction
Medición cuántica en la base computacional.
Parámetros
label - etiqueta de cadena opcional para esta instrucción.
En relación con las mediciones, existe una Reset que no produce datos clásicos, sino que ordena al hardware que devuelva el qubit al estado . Se supone que esto ocurre de forma incoherente y colapsa cualquier entrelazamiento.
Restablecer
class qiskit.circuit.Reset(label=None)
Bases: SingletonInstruction
Restablecer incoherentemente un qubit al estado .
Parámetros
label - etiqueta de cadena opcional de esta instrucción.
Se puede ordenar al hardware que aplique un periodo de inactividad en tiempo real a un qubit determinado. Un circuito programado (véase qiskit.transpiler) incluirá todos los tiempos muertos de los qubits explícitamente en términos de este Delay. BoxOp también puede tener una duración explícita adjunta, en su campo BoxOp.duration .
Retardo
class qiskit.circuit.Delay(duration, unit=None)
Bases: Instruction
No hace nada y sólo retrasa/espera/espera durante un tiempo especificado.
Parámetros
- duración - la duración de la duración. Si se trata de un
Exprdebe ser una expresión constante de tipoDurationy el parámetrounitdebe omitirse (o DEBE ser "expr" si se especifica). - unidad - la unidad de la duración, si
durationes un valor numérico. Debe ser"dt", una unidad de segundos con prefijo SI, o "expr".
Eleva
CircuitError - Se especificó una expresión duration con un tipo resuelto que no está basado en el tiempo o se especificó incorrectamente unit .
Las duraciones de los retardos pueden especificarse con tiempos constantes concretos o con "expresiones de duración" de resolución retardada construidas a partir de objetos expr.Stretch objetos. Para más información, véase Programación con resolución retardada.
La instrucción Barrier puede abarcar un número arbitrario de qubits y clbits, y es un no-op en hardware. Durante la transpilación y la optimización, sin embargo, bloquea cualquier optimización que "cruce" la barrera; es decir, en:
from qiskit.circuit import QuantumCircuit
qc = QuantumCircuit(1)
qc.x(0)
qc.barrier()
qc.x(0)está prohibido que el optimizador anule las dos instrucciones .
Barrera
class qiskit.circuit.Barrier(num_qubits, label=None)
Bases: Instruction
Una directiva para la compilación de circuitos para separar las piezas de un circuito de modo que cualquier optimización o reescritura se limite a actuar sólo entre las barreras.
Esto también aparecerá en las visualizaciones como un marcador visual.
Parámetros
La instrucción Store es particularmente especial, ya que permite escribir el resultado de una expresión de cálculo clásico en tiempo real (an expr.Expr) en una variable local clásica (a expr.Var). No hace falta Qubit ni Clbit pero tiene un operando explícito lvalue y rvalue.
Por ejemplo, para determinar la paridad de una cadena de bits cr y almacenarla en otro registro creg, la instrucción Store se puede utilizar de la siguiente manera:
parity = expr.lift(cr[0])
for i in range(1,n):
parity = expr.bit_xor(cr[i], parity)
qc.store(creg[0], parity)Tienda
class qiskit.circuit.Store(lvalue, rvalue)
Bases: Instruction
Almacenamiento manual de algún valor clásico en una posición de memoria clásica.
Esta es una primitiva de bajo nivel del manejo de expresiones clásicas (similar a como Measure es una primitiva para la medición cuántica), y no es seguro para la subclase.
Parámetros
- lvalue (expr.Expr) – la posición de memoria en la que se almacena.
- rvalue (expr.Expr) – el resultado de la expresión que se almacena.
lvalue
Obtener el valor l Expr que se está almacenando.
rvalue
Obtener el nodo r-value Expr que se está escribiendo en el nodo l-value.
Cálculo clásico en tiempo real
Documentación a nivel de módulo sobre el funcionamiento de los sistemas de variables, expresiones y tipos, los objetos utilizados para representarlos y las operaciones clásicas disponibles.
Trabajar con datos clásicos mecanografiados en tiempo real
El sitio QuantumCircuit métodos para trabajar con estas variables en el contexto de un circuito único.
Qiskit tiene un soporte rudimentario de bajo nivel para representar cálculos clásicos en tiempo real, que ocurren durante la ejecución de la QPU y afectan a los resultados. El soporte de hardware para estos conceptos también es relativamente reciente, así que ten en cuenta que tendrás que trabajar en estrecha colaboración con la documentación de tu proveedor de hardware para sacar el máximo partido de cualquier cálculo clásico en tiempo real.
Estos cálculos en tiempo real están representados por el sistema de expresiones y tipos en qiskit.circuit.classical. A alto nivel, todas las expresiones en tiempo real se representan mediante un nodo Expr que forma parte de un "árbol" de expresiones, al que se asocia una dirección Type bien definida en cada nivel. Consulte la documentación a nivel de módulo para obtener más información sobre las representaciones internas de estas clases.
El resultado de un tiempo real Expr puede utilizarse directamente en determinados lugares. Actualmente se limita a las condiciones de IfElseOp y WhileLoopOpy el objetivo de SwitchCaseOp. El resultado también puede almacenarse en una posición de almacenamiento clásica tipada, utilizando la instrucción Store (o su constructor QuantumCircuit.store() constructor), respaldada por un expr.Var nodo.
Un circuito puede contener almacenes clásicos manuales, representados internamente por el Var nodo del Expr árbol. Tienen un tipo clásico adjunto (como cualquier otra expresión). Pueden declararse e inicializarse en cada ejecución del circuito (add_var()), o ser entradas al circuito (add_input()).
Parametrización en tiempo de compilación
Varias instancias paramétricas Instruction en Qiskit pueden ser parametrizadas en formas que están diseñadas para ser resueltas en tiempo de compilación. Se caracterizan por el uso de los Parameter y ParameterExpression clases.
Parameter(nombre[, uuid] ) | Un parámetro simbólico en tiempo de compilación. |
ParameterExpression( [nombre_mapa, expresión] ) | Una expresión de parámetro. |
La principal diferencia con las variables expr.Var utilizadas en la computación clásica en tiempo real es que ParameterExpression es una representación simbólica de una expresión matemática. La semántica de la expresión es la de las matemáticas regulares sobre los números reales continuos (y, en casos limitados, sobre los números complejos). Por el contrario, Var es un manejador de una variable almacenada en un ordenador clásico, como un valor de punto flotante o un entero de ancho fijo, que son siempre discretos.
En otras palabras, se puede esperar ParameterExpression para hacer simplificaciones simbólicas que son válidas en matemáticas, como simplificar . Tal simplificación no es válida en aritmética de coma flotante, y expr.Expr no lo hará.
La parte de "tiempo de compilación" de estos parámetros significa que normalmente querrá "asignar" valores a los parámetros antes de enviar el circuito para su ejecución. Estos parámetros se pueden utilizar normalmente en cualquier lugar que espere un ángulo matemático (como los parámetros de una puerta de rotación), con la advertencia de que el hardware normalmente requerirá que se asignen a un valor de tipo clásico adecuado antes de la ejecución. Puede realizar esta tarea utilizando QuantumCircuit.assign_parameters().
Es posible que desee utilizar muchos parámetros relacionados entre sí. Para hacer esto más fácil (y para evitar que usted tenga que inventar muchos nombres), puede utilizar el constructor de conveniencia ParameterVector. Los elementos del vector son todos instancias Parameter de una subclase especial ParameterVectorElement.
ParameterVector(nombre[, longitud] ) | Un contenedor de muchos objetos Parameter relacionados. |
ParameterVectorElement(vector, índice[, uuid] ) | Un elemento de a ParameterVector. |
Control de flujo en circuitos
En QuantumCircuitel flujo de control clásico está representado por Instructions, que son subclases de ControlFlowOp.
ControlFlowOp(*args, **kwargs) | Clase abstracta para encapsular todas las operaciones de flujo de control. |
Para mayor comodidad, existe una frozenset que contiene los atributos Instruction.name atributos de cada una de las operaciones de flujo de control.
qiskit.circuit.CONTROL_FLOW_OP_NAMES
Conjunto de los nombres de instrucción de las operaciones de flujo de control conocidas de Qiskit.
La función get_control_flow_name_mapping() permite acceder a las clases de operación de flujo de control asociadas a cada nombre.
get_control_flow_name_mapping
qiskit.circuit.get_control_flow_name_mapping()
Devuelve un diccionario que asigna los nombres de las operaciones de flujo de control a sus clases correspondientes."
Ejemplos
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 estas operaciones de flujo de control (IfElseOp, WhileLoopOp, SwitchCaseOp ForLoopOp, y BoxOp) tienen un estado específico que define las condiciones y estrategias de ramificación, pero contienen todos los diferentes bloques de subcircuitos que podrían introducirse en su blocks propiedad.
IfElseOp(condición, [cuerpo_verdadero, cuerpo_falso,...] ) | Una operación de circuito que ejecuta un programa (true_body) si una condición (condition) evaluada es verdadera, y opcionalmente evalúa otro programa (false_body) en caso contrario. |
WhileLoopOp(condición, cuerpo[, etiqueta] ) | Una operación de circuito que ejecuta repetidamente un subcircuito (body) hasta que una condición (condition) se evalúa como Falso. |
SwitchCaseOp(objetivo, casos, *[, etiqueta] ) | Operación de circuito que ejecuta un bloque de circuito concreto basándose en la correspondencia de un target dado con una lista ordenada de values. |
ForLoopOp(conjunto_índice, parámetro_bucle, cuerpo[,...] ) | Una operación de circuito que ejecuta repetidamente un subcircuito (body) parametrizado por un parámetro loop_parameter a través del conjunto de valores enteros proporcionados en indexset. |
BoxOp(cuerpo[, duración, unidad, etiqueta, anotaciones] ) | Una "caja" de alcance de operaciones en un circuito que se tratan atómicamente en el contexto mayor. |
El SwitchCaseOp también comprende un valor especial:
qiskit.circuit.CASE_DEFAULT
Valor predeterminado: <default case>
Un objeto especial que representa el caso "por defecto" de una sentencia switch. Si lo utiliza como objetivo de caso, debe ser el último caso y coincidirá con todo lo que no haya coincidido ya. Cuando se utiliza la interfaz del constructor de QuantumCircuit.switch()también se puede acceder a él como atributo DEFAULT del objeto constructor de casos vinculado.
Además de las operaciones de flujo de control de la estructura de bloques, también hay dos instrucciones especiales que afectan al flujo de control cuando se encuentran dentro de bucles. Corresponden a los usos típicos de las sentencias break y continue en los lenguajes de programación clásicos.
BreakLoopOp(num_qubits, num_clbits[, label] ) | Una operación de circuito que, cuando se encuentra, salta al final del bucle envolvente más cercano. |
ContinueLoopOp(num_qubits, num_clbits[, label] ) | Operación de circuito que, cuando se encuentra, se desplaza a la siguiente iteración del bucle adyacente más cercano. |
Las representaciones de las clases están documentadas aquí, pero tenga en cuenta que la construcción manual de estas clases es una operación de bajo nivel que no esperamos que los usuarios necesiten hacer con frecuencia.
Los usuarios deben leer Añadir flujo de control a los circuitos para conocer los flujos de trabajo recomendados para construir circuitos habilitados para flujo de control.
Dado que ControlFlowOp son también subclases de Instruction subclases, esto significa que la forma en que se almacenan en CircuitInstruction las instancias los tiene "aplicados" a una secuencia de qubits y clbits en sus qubits y clbits atributos. Esto puede dar lugar a sutiles problemas de coherencia de datos: el Qubit y Clbit utilizados dentro de los bloques de subcircuito de las operaciones de flujo de control no serán necesariamente idénticos a los objetos correspondientes en el bloque CircuitInstruction. Cualquier código que consuma operaciones de flujo de control en Qiskit necesita ser consciente de esto; dentro de un subcircuito, deberías tratar subcircuit.qubits[i] como si fuera realmente outer_instruction.qubits[i], y así sucesivamente. Puede generar una tabla de consulta fácil para esto haciendo:
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` ...Recuerde que tendrá que propagar esta información si recurre a subbloques de operaciones de flujo de control.
Todos los bloques de subcircuitos de un ControlFlowOp deben contener el mismo número de Qubits y Clbits, referidos a los mismos bits exteriores en el mismo orden, de forma que funcione el bucle zip dado en el bloque de código anterior. Los objetos del circuito interno Bit no tienen por qué ser literalmente los mismos objetos. Cuando se utiliza la interfaz del constructor de flujo de control (que, no se puede enfatizar lo suficiente, es muy recomendable para los usuarios), los constructores se encargarán de que los objetos de bits internos sean idénticos a los objetos de bits externos; el qubit_map en el bloque de código anterior siempre será un mapeo {x: x}, pero si usted está consumiendo los bloques, debe estar preparado para el caso de que se requiera el mapeo.
Cualquier ClassicalRegisters utilizado en un subcircuito de flujo de control también debe estar presente en todos los bloques que lo contengan (es decir, cualquiera que contenga operaciones de flujo de control y el circuito más externo), y todos los bloques del mismo subcircuito de flujo de control deben contener los mismos registros ControlFlowOp deben contener los mismos registros. De nuevo, la interfaz del constructor se encargará de que esto sea así (o producirá un error eager si no puede).
Cuando se utiliza la construcción de bajo nivel, los bloques QuantumCircuit deben cerrarse manualmente sobre cualquier variable de computación clásica en tiempo real de ámbito externo que utilicen. Esto se marca por estar en el iter_captured_vars() iterador de ese bloque. Las bibliotecas que construyan estos bloques manualmente tendrán que rastrear estas capturas cuando construyan bloques de circuitos de flujo de control y añadirlas al bloque utilizando add_capture() (o el argumento del constructor captures ), pero el código de usuario utilizará normalmente la interfaz del constructor de flujo de control, que se encarga de ello automáticamente.
Consulte la documentación de construcción con flujo de control para obtener más información sobre cómo construir circuitos con flujo de control.
Anotaciones locales de instrucciones
La discusión a nivel de módulo del marco de anotaciones, incluyendo cómo definir anotaciones personalizadas, y cómo el sistema interactúa con el compilador y con la serialización a otros formatos.
Algunas instrucciones de circuito pueden "anotarse" con anotaciones locales de instrucción. A partir de Qiskit 2.1.0, esto se limita a BoxOp. Todas las anotaciones son subclases de un objeto base que define la interfaz, pero suelen representar análisis y comandos totalmente personalizados.
Annotation(*args, **kwargs) | Una anotación arbitraria para las instrucciones. |
Investigación de las relaciones de conmutación
Si dos operaciones de un circuito conmutan, podemos intercambiar el orden en que se aplican. Esto puede permitir optimizaciones y simplificaciones, por ejemplo, si permite fusionar o cancelar compuertas:
┌─────────┐ ┌─────────┐ ┌─────────┐
q_0: ┤ Rz(0.5) ├──■──┤ Rz(1.2) ├──■── q_0: ┤ Rz(1.7) ├
└─────────┘┌─┴─┐└──┬───┬──┘┌─┴─┐ = └──┬───┬──┘
q_1: ───────────┤ X ├───┤ X ├───┤ X ├ q_1: ───┤ X ├───
└───┘ └───┘ └───┘ └───┘La realización de estas optimizaciones forma parte del transpilador, pero las herramientas para investigar las conmutaciones están disponibles en el archivo CommutationChecker.
CommutationChecker([...]) | Comprobar conmutaciones de dos operaciones. |
Programación de resolución diferida
Normalmente, la salida del compilador de Qiskit no puede ejecutarse directamente en una QPU. En primer lugar, es probable que pase por algún compilador de nivel de pulsos específico del proveedor, que convierte las puertas y las mediciones en señales para la electrónica de control de la QPU. Aunque Qiskit Target puede representar algunas de las restricciones de tiempo que tendrán estos pulsos, por lo general no es del todo completo. Esto es especialmente cierto cuando se trata de circuitos dinámicos (operaciones feed-forward); los retrasos inducidos por los componentes clásicos dependen a menudo de detalles de post-optimización de bajo nivel de los compiladores backend, y no pueden ser conocidos por Qiskit.
En estas situaciones, el usuario puede seguir controlando la programación relativa de los impulsos, por ejemplo para el desacoplamiento dinámico, utilizando duraciones de "estiramiento". Se construyen mediante QuantumCircuit.add_stretch()e interactúan con el sistema de expresión clásico descrito en qiskit.circuit.classicalaunque no son mutables en tiempo real.
Por ejemplo, podemos añadir tramos y cajas para crear un sistema en el que se apliquen dos secuencias separadas de desacoplamiento dinámico al mismo qubit, mientras que otro par de qubits sufre un retraso de duración desconocida. Las dos secuencias deben tener la misma longitud, aunque internamente los impulsos DD concretos tengan longitudes 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)Creación de instrucciones personalizadas
Si desea crear instrucciones o puertas únicas y sencillas que se añadirán a un circuito, y los bloques sólo se utilizan con fines de visualización o agrupación, la forma más sencilla de crear una instrucción o puerta personalizada es simplemente construir su definición como un bloque QuantumCircuity, a continuación, utilizar to_instruction() o to_gate() según convenga. Los resultados se pueden dar directamente a QuantumCircuit.append() en el circuito mayor. Estos métodos crearán bases Instruction o Gate cuyo atributo definition es el circuito suministrado, lo que significa que será automáticamente accesible al transpilador y a otras funciones de Qiskit que intenten descomponer circuitos.
Tenga en cuenta que las instrucciones independientes y las puertas sólo deben actuar sobre qubits y clbits; las instrucciones que necesiten utilizar un flujo de control complejo tendrán que alinearse en QuantumCircuit utilizando compose().
Creación de subclases de instrucción
Las clases base Instruction, Gate y ControlledGate están diseñadas para que sea seguro subclasificarlas, y tienen puntos de enganche para que las subclases las implementen. Si su puerta personalizada no tiene parámetros ni estado, puede que también desee derivar de la clase singleton correspondiente en qiskit.circuit.singletoncomo SingletonGate. Debe consultar la documentación en qiskit.circuit.singleton para conocer métodos adicionales y puntos de enganche para la maquinaria singleton.
Las subclases normalmente deberían definir un constructor por defecto que llame al constructor :class`super` con los argumentos correctos para su instrucción. Es permisible tener un estado extra en la clase, pero tus subclases se integrarán de forma más fiable con el resto de la maquinaria Qiskit si sólo dependes de sus parámetros Instruction.paramsy estos parámetros son puramente ángulos de puerta.
Las subclases de Instruction (o una de sus subclases) deberían implementar el método privado Instruction._define() que rellena perezosamente la caché oculta _definition que respalda el método público definition público.
_define
Instruction._define()
Rellenar el campo _definition de esta caché Instruction.
Las subclases deben implementar este método para proporcionar una construcción perezosa de su atributo público definition público. Una subclase puede utilizar su params en el momento de la llamada. El método debe rellenar _definition con un QuantumCircuit y no devolver un valor.
En las subclases de ControlledGateel método _define() debe implementar la descomposición sólo para el estado de control all-ones. La maquinaria de ControlledGate.definition modificará esto para manejar el estado de control real.
Si la subclase utiliza la maquinaria singleton, tenga en cuenta que _define() será llamado inmediatamente después de que la sentencia class-body haya sido ejecutada, para producir el objeto de definición del objeto singleton canónico. Esto significa que su definición sólo debe utilizar puertas que ya estén definidas; si está escribiendo una biblioteca con muchas puertas singleton, tendrá que ordenar sus archivos e importaciones para asegurarse de que esto es posible.
Las subclases de Gate también es probable que deseen anular el método de instancia de Numpy array-protocolo, __array__. Es utilizado por Gate.to_matrix()y tiene la firma
__array__
object.__array__(dtype=None, copy=None)
Devuelve una matriz Numpy que representa la puerta. Puede utilizar el campo params y puede asumir que se trata de valores numéricos (asumiendo que la subclase lo espera) y no de parámetros en tiempo de compilación.
Para una mayor eficiencia, el array devuelto debe tener por defecto un dtype de complex.
Si su subclase personalizada tiene representaciones naturales de sus formas controladas o inversas, es posible que también desee anular el método inverse() y control() .
Como ejemplo de definición de una puerta personalizada ; es decir, una rotación de un solo ángulo alrededor del eje . Esto es esencialmente RZXGate, si los qubits fueran al revés, así que escribiremos nuestra definición en términos de eso. Somos paramétricos, por lo que no podemos ser un singleton, pero somos unitarios, por lo que deberíamos ser un 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)En este ejemplo, hemos definido una base en términos de RZXGate, pero para permitir descomposiciones más rápidas a un rango de bases, podríamos querer añadir algunas equivalencias más a SessionEquivalenceLibrary. Tenga en cuenta que la BasisTranslator buscará a través de todas las equivalencias posibles en todas las profundidades posibles, por lo que proporcionar una equivalencia en términos de (digamos) XGate hará que las descomposiciones en términos de RXGate también estarán disponibles.
Añadamos una equivalencia en términos de , y para un parámetro simbólico arbitrario:
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)Después de esto, mientras dure la sesión de interpretación de Python, los traductores como BasisTranslator encontrarán nuestra nueva definición en su búsqueda.
Trabajar con objetos a nivel de circuito
Conversión de circuitos abstractos en circuitos físicos
Un resumen QuantumCircuit no puede ejecutarse de forma fiable en hardware. Es posible que pueda utilizar algunos de los simuladores de alto nivel enlazados en Simulación de circuitos para obtener resultados rápidos para circuitos a pequeña escala, pero para ejecutar circuitos a escala de servicios públicos, tendrá que utilizar hardware real, lo que implica compilar a un circuito físico.
La función de alto nivel para hacerlo es transpile()toma un circuito abstracto y un hardware backend o target, y devuelve un circuito físico. Para obtener más acceso y control sobre las etapas de los pases que se ejecutarán, utilice generate_preset_pass_manager() para construir un StagedPassManager primero, que luego podrás modificar.
La maquinaria completa de transpilación y compilación se describe en detalle en la documentación del módulo qiskit.transpiler y los detalles de todos los pases incorporados en Qiskit están disponibles en qiskit.transpiler.passes.
Simulación de circuitos
Aunque no forma parte de la qiskit.circuit interfaz, una de las necesidades más comunes es obtener resultados rápidos de simulación para QuantumCircuit objetos. Esta sección proporciona un punto de partida rápido a otros lugares de la documentación para encontrar la información pertinente.
Para circuitos unitarios, puede simular los efectos en el estado pasando el QuantumCircuit directamente al constructor Statevector constructor por defecto. De forma similar, puedes obtener una matriz unitaria que represente el circuito como un operador pasándola al constructor por defecto Operator constructor por defecto. Si dispone de un circuito físico, puede que prefiera pasarlo al método Operator.from_circuit() para aplicar las transformaciones del QuantumCircuit.layout para devolverlo al espacio "abstracto" de los qubits.
Para una experiencia de simulación más parecida a la del backend, existen implementaciones respaldadas por simulador de todas las interfaces de hardware de Qiskit. En particular, podría interesarle:
BasicProvidery los datos en bruto que puede devolverte.StatevectorSimulatorpara envolverStatevector- El sitio
qiskit_aerpara capacidades de simulación completas y de alto rendimiento. StatevectorSampleryStatevectorEstimatorpara implementaciones de referencia respaldadas por simuladores de las primitivas Qiskit.
Definición de relaciones de equivalencia
Una tarea habitual a la hora de asignar circuitos abstractos a hardware físico y optimizar el resultado es encontrar relaciones de equivalencia que asignen una puerta a un conjunto de bases diferente. Qiskit almacena esta información en una clase de base de datos llamada EquivalenceLibrary.
EquivalenceLibrary( [base] ) | Una biblioteca que proporciona un mapeo unidireccional de Gates a sus implementaciones equivalentes como QuantumCircuits. |
Qiskit incluye un amplio conjunto de relaciones de equivalencia predefinidas para todas sus puertas estándar. Esta biblioteca base se llama StandardEquivalenceLibraryy debe tratarse como inmutable.
qiskit.circuit.StandardEquivalenceLibrary
A EquivalenceLibrary que almacena todas las relaciones de puerta estándar incorporadas de Qiskit. No debe modificarlo, sino crear uno propio EquivalenceLibrary utilizando éste como base, o modificar el estado global SessionEquivalenceLibrary.
Qiskit también define un objeto de estado global compartido, SessionEquivalenceLibrary , que es la equivalencia por defecto usada por varios lugares en Qiskit, más notablemente el pase de transpilador BasisTranslator . Puede añadir sus propias equivalencias utilizando su método add_equivalence(), y serán automáticamente recogidas por las instancias por defecto de BasisTranslator .
qiskit.circuit.SessionEquivalenceLibrary
La instancia por defecto de EquivalenceLibraryque será utilizada por la mayoría de los objetos Qiskit si no se especifica manualmente ninguna biblioteca. Puede añadir equivalencias utilizando add_equivalence(). Hereda todas las reglas incorporadas de StandardEquivalenceLibrary.
Aplicar el efecto Pauli a un circuito
Hay dos tipos principales de ruido cuando se ejecutan circuitos cuánticos. El primero es el ruido estocástico, o incoherente, que se debe principalmente a la interacción no deseada entre el procesador cuántico y el entorno externo en el que reside. El segundo se conoce como error coherente, y estos errores surgen debido al control imperfecto de un sistema cuántico. Puede tratarse de términos no deseados en el Hamiltoniano de un sistema, es decir, una evolución unitaria incorrecta, o de errores derivados de un control temporal incorrecto del sistema cuántico, lo que incluye cosas como formas de pulso incorrectas para las puertas.
El giro de Pauli es una técnica de supresión de errores cuánticos que utiliza la aleatorización para transformar los errores coherentes en errores estocásticos combinando los resultados de muchos circuitos aleatorios, pero lógicamente equivalentes. Qiskit proporciona una función para aplicar el giro de Pauli a un circuito dado para puertas estándar de dos qubits. Para más detalles, consulte la documentación de la función que figura a continuación:
pauli_twirl_2q_gates
qiskit.circuit.pauli_twirl_2q_gates(circuit, twirling_gate=None, seed=None, num_twirls=None, target=None)
Crea copias de un circuito dado con Pauli twirling aplicado alrededor de puertas de dos qubits especificados.
Si está ejecutando esta función con la intención de girar un circuito para ejecutarlo en hardware, esta puede no ser la forma más eficiente de realizar el giro. Especialmente si el proveedor de hardware ha implementado la primitives interfaz de ejecución con SamplerV2 y EstimatorV2 lo más probable es que esta no sea la mejor manera de aplicar el giro a su circuito y usted querrá referirse a la implementación de SamplerV2 y/o EstimatorV2 para el proveedor de hardware especificado.
Si la intención de esta función es que se ejecute después de transpile() o PassManager.run() el argumento opcional target puede ser usado para que las puertas Pauli de 1 qubit insertadas sean sintetizadas para ser compatibles con el dado Target para que el circuito o circuitos de salida sigan siendo compatibles.
Parámetros
- circuit (QuantumCircuit) – El circuito para girar
- twirling_gate (None | str |Gate |list[str] | list[Gate]) – La puerta a girar, por defecto None que significa girar todas las puertas por defecto:
CXGate,CZGate,ECRGateyiSwapGate. Si se proporciona, puede ser una única puerta o una lista de puertas, ya sea como objeto puerta o como nombre de cadena. Actualmente sólo se admiten los nombres "cx", "cz", "ecr" e "iswap". Si se proporciona un objeto puerta fuera de las puertas por defecto, debe tener una matriz definida en su métodoto_matrixpara que la puerta pueda ser girada. Si no se puede calcular una configuración de giro válida, esa puerta en particular se ignorará silenciosamente y no se girará. - seed (int | None) – Una semilla entera para el generador de números aleatorios utilizado internamente por esta función. Si se especifica, debe estar comprendido entre 0 y 18.446.744.073.709.551.615.
- num_twirls (int | None) – El número de circuitos giratorios a construir. El valor predeterminado es
Noney devolverá un único circuito. Si es un número entero se devolverá una lista de circuitos con num_twirls circuitos. - target (Target | None) – Si se especifica una
Targetpara ejecutar la descomposición de un qubit como parte del giro Pauli para optimizar y asignar las puertas pauli añadidas al circuito al objetivo especificado.
Devuelve
Una copia del circuito dado con Pauli twirling aplicado a cada instancia de la puerta twirling especificada.
Tipo de retorno
Excepciones
Casi todas las funciones y métodos del circuito lanzarán un CircuitError cuando se encuentren con un error particular del uso de Qiskit (a diferencia de los problemas de tipado o indexación, que normalmente mostrarán el correspondiente error estándar de Python ).
CircuitError
exception qiskit.circuit.CircuitError(*message)
Bases: QiskitError
Clase base para los errores que se producen al procesar un circuito.
Establece el mensaje de error.
Convenciones de circuitos
Cuando se construyen circuitos a partir de objetos abstractos y matrices más concretas, existen varias convenciones posibles en torno al etiquetado y ordenación de bits, y a cómo se realiza el producto tensorial abstracto en el álgebra matricial concreta.
Las convenciones de Qiskit son:
- en las representaciones de cadenas de bits, los bits se etiquetan con el bit situado más a la derecha de la cadena denominado y el bit situado más a la izquierda de la cadena de bits denominado .
- cuando se utilizan números enteros como índices de especificadores de bits en funciones de construcción de circuitos, el número entero se trata como un índice en
QuantumCircuit.qubits(oclbits). - al dibujar circuitos, ponemos los bits de menor índice en la parte superior.
- en representaciones vectoriales de estado, realizamos el producto tensorial abstracto como el producto de Kronecker, y ordenamos los argumentos a este de tal manera que la amplitud del estado de base computacional , donde es la cadena de bits interpretada como un entero, se encuentra en la ubicación
statevector[x]. - al controlar una puerta, el qubit o qubits de control se colocan en primer lugar en la lista de argumentos, por ejemplo, en la llamada
qc.cx(0, 1), el qubit 0 será el control y el qubit 1 será el objetivo. Del mismo modo, en la llamada manualqc.append(CXGate(), [0, 1]), el qubit 0 será el control y el qubit 1 será el objetivo.
Ilustremos estas convenciones con algunos ejemplos.
Etiquetado de bits
Toma el circuito:
from qiskit import QuantumCircuit
qc = QuantumCircuit(5, 5)
qc.x(0)
qc.x(1)
qc.x(4)
qc.measure(range(5), range(5))Esto invierte los estados de los qubits 0, 1 y 4 de a , luego mide todos los qubits en el correspondiente clbit utilizando la base computacional ( ). Si se simula sin ruido, la cadena de bits de salida de este circuito será cada vez; los qubits 0, 1 y 4 se voltean, y los valores "uno" de la cadena de bits están en los dígitos cero, primero y cuarto por la derecha.
En Qiskit, escribiríamos el estado del qubit inmediatamente antes de la medición en abreviatura ket-notation como . Nótese que la etiqueta ket coincide con la cadena de bits clásica, y tiene el valor numérico binario de 19.
Si dibujamos este circuito, veremos que Qiskit coloca el qubit zeroth en la parte superior del dibujo del circuito:
qc.draw("mpl")
Representaciones matriciales
Los vectores de estado se definen según la convención de que, para un sistema de dos niveles, la relación entre la representación abstracta y la representación matricial es tal que
donde y son números complejos. Almacenamos el vector de estado como 1D Numpy ndarray con datos sv = [alpha, beta], es decir sv[0] == alpha y sv[1] == beta; nótese que los índices en el vector estado coinciden con las etiquetas ket.
Construimos el producto tensorial de dos estados qubit en álgebra matricial utilizando el producto de Kronecker, con el qubit 0 a la derecha y el qubit 1 a la izquierda, de forma que el estado base (donde es la interpretación entera de la cadena de bits) tiene su término distinto de cero en el vector de estado sv en 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] == 1Esto se traslada a la representación matricial de los operadores y se une a las convenciones sobre los órdenes de bits de los operadores controlados. Por ejemplo, la forma matricial de CXGate es
import numpy
from qiskit.circuit.library import CXGate
numpy.array(CXGate())Esto puede ser diferente a otras representaciones matriciales que hayas visto para , pero recuerda que la elección de la representación matricial es convencional, y esta forma coincide con las convenciones de Qiskit de que los qubits de control van primero y el producto tensorial se representa de forma que haya una correspondencia entre el índice de la "amplitud uno" y el valor de la cadena de bits de un estado.
En el caso de múltiples controles para una puerta, como para CCXGateel argumento ctrl_state se interpreta como el valor de la cadena de bits de los qubits de control, utilizando las mismas convenciones de etiquetado basadas en ceros. Por ejemplo, dado que el valor por defecto ctrl_state es la cadena de bits todo-uno, podemos ver que la forma matricial de CCXGate con ctrl_state = 1 es la misma que si tomáramos el estado de control todo-uno CCXGatepero invirtiendo el valor del qubit de control de mayor índice en la entrada y en la existencia de la puerta:
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)En ambos casos, la forma matricial de CCXGate en ctrl_state = 1 es: