Anotaciones de circuitos
qiskit.circuit.annotation
Este módulo contiene la infraestructura para trabajar con anotaciones de circuitos personalizados.
qiskit.circuit.AnnotationLa clase principal a la que acceden los usuarios es la clase base, que también se reexporta desde este módulo.
Subclases de anotaciones personalizadas
La Annotation clase está pensada para que se creen subclases a partir de ella. Las subclases deben establecer su namespace campo. Esto puede ser específico de una instancia o estático para toda una subclase. El espacio de nombres se utiliza como parte del mecanismo de distribución, tal y como se describe en «Espacios de nombres ».
Las comprobaciones de igualdad de circuitos también comparan las anotaciones de los objetos en función del orden. Es probable que desee implementar el método __eq__() en cualquier subclase.
Si desea que su anotación pueda serializarse a través de QPY o :ref:` OpenQASM 3 <qiskit-qasm3> `, debe proporcionar implementaciones separadas de los métodos de serialización y deserialización, tal y como se explica en Serialización y deserialización.
Espacio de nombres
El "espacio de nombres" de una anotación se utiliza como clave de búsqueda cuando cualquier consumidor está decidiendo qué manejador invocar. Esto incluye en contextos de serialización QPY y OpenQASM 3, pero en general, los pases del transpilador también mirarán los namespaces de las anotaciones para determinar si son relevantes, etc.
Puede tratarse de un identificador estándar de Python (por ejemplo, my_namespace), o una lista de identificadores separados por puntos (p. ej., my_namespace.subnamespace). Todos los usuarios de anotaciones utilizan el espacio de nombres para determinar qué controlador debe invocarse.
Una función independiente permite iterar por los espacios de nombres y los espacios de nombres padre en orden de prioridad de más específico a menos específico.
iter_namespaces
qiskit.circuit.annotation.iter_namespaces(namespace)
Un iterador sobre todos los espacios de nombres que se pueden utilizar para buscar el espacio de nombres dado.
Esto incluye el espacio de nombres y todos los padres, incluido el espacio de nombres raíz de cadena vacía.
Ejemplos:
from qiskit.circuit.annotation import iter_namespaces
assert list(iter_namespaces("hello.world")) == ["hello.world", "hello", ""]Parámetros
namespace (str)
Tipo de retorno
Serialización y deserialización
Las anotaciones representan datos completamente personalizados, que pueden persistir tras la compilación. Esto puede incluir datos que deben ser serializados para su posterior consumo, como datos adicionales que son interpretados por un compilador backend. El formato binario nativo QPY de Qiskit (véase qiskit.qpy) admite el concepto de anotaciones arbitrarias en sus cargas útiles a partir de la versión 15. En OpenQASM 3 (véase qiskit.qasm3), las anotaciones son una característica central del lenguaje, y el soporte de importación/exportación de Qiskit para OpenQASM 3 incluye la serialización de anotaciones.
Sin embargo, dado que las anotaciones suelen ser subclases personalizadas y Qiskit no las reconoce, no podemos contar con compatibilidad integrada para la serialización. En lo que respecta a la deserialización, Qiskit no suele disponer de un objeto existente Annotation desde el que llamar a los métodos de deserialización. También se prevé que las anotaciones puedan estar relacionadas con algún estado compartido desconocido para Qiskit dentro del contexto de un circuito determinado.
Por todas estas razones, la serialización y deserialización de anotaciones es manejada por objetos personalizados, que deben ser pasados en los puntos de interfaz de las funciones de serialización relevantes. Por ejemplo, en QPY, el argumento annotation_factories en qpy.dump() y qpy.load() se utilizan para pasar serializadores.
QPYSerializador
class qiskit.circuit.annotation.QPYSerializer
Bases: ABC
La interfaz para los serializadores y deserializadores de Annotation objetos en QPY.
Para más información sobre QPY, véase qiskit.qpy.
Esta clase de definición de interfaz está diseñada para ser subclasificada. Los métodos individuales describen sus contratos y cómo serán llamados.
Durante la serialización y deserialización de QPY, la lógica principal de QPY llamará a una función de fábrica para crear instancias de subclases de esta clase. El valor de retorno de una función de fábrica dada se utilizará en un contexto de serialización o deserialización, pero no en ambos.
La estructura de las llamadas durante la serialización de un circuito único es:
- muchas llamadas a
dump_annotation(), que compartirán todas el mismonamespaceargumento, el cual será siempre un prefijo (no estricto) de todos losAnnotationobjetos proporcionados. - una llamada a
dump_state().
La estructura general de las llamadas durante la deserialización de un circuito único a partir de una carga útil QPY es la siguiente:
- una llamada a
load_state(), pasando unnamespace(con el mismo comportamiento de prefijación no estricto que la forma de "serialización"). - muchas llamadas a
load_annotation(), correspondientes a anotaciones serializadas bajo esa búsqueda de prefijo de espacio de nombres.
Al subclasificar esto, recuerde que QPY está pensado para tener estrictas garantías de compatibilidad con versiones anteriores, y se recomienda encarecidamente que las subclases de serialización de anotaciones mantengan esto. En particular, se sugiere que cualquier serializador no trivial incluya información de "versión" para el serializador en su "estado" total (véase dump_state()), y la deserialización debe hacer todo lo posible para apoyar la compatibilidad con versiones anteriores del mismo serializador.
QPYFromOpenQASM3Serializer
class qiskit.circuit.annotation.QPYFromOpenQASM3Serializer(inner)
Bases: QPYSerializer
Un adaptador que convierte un OpenQASM3Serializer en un QPYSerializer.
Esto funciona porque los serializadores de anotaciones de OpenQASM 3 deben ser apátridas y devolver UTF-8-encoded líneas de texto individuales, que es un subconjunto de lo que permite QPY.
Normalmente se crea uno de estos utilizando el método as_qpy() de un serializador de anotaciones de OpenQASM 3.
Ejemplos:
Las instancias de esta clase se pueden llamar como una función sin argumentos y se devuelven a sí mismas. Esto le permite utilizarlos directamente como una función de fábrica a los puntos de entrada QPY, tales como:
import io
from qiskit.circuit import OpenQASM3Serializer, Annotation
from qiskit import qpy
class MyAnnotation(Annotation):
namespace = "my_namespace"
class MySerializer(OpenQASM3Serializer):
def dump(self, annotation):
if not isinstance(annotation, MyAnnotation):
return NotImplemented
return ""
def load(self, namespace, payload):
assert namespace == "my_namespace"
assert payload == ""
return MyAnnotation()
qc = QuantumCircuit(2)
with qc.box(annotations=[MyAnnotation()]):
qc.cx(0, 1)
with io.BytesIO() as fptr:
qpy.dump(fptr, qc, annotation_serializers = {"my_namespace": MySerializer().as_qpy()})Esto es seguro, sin devolver instancias separadas, porque la base OpenQASM 3 serializadores son necesariamente sin estado.
Parámetros
inner (OpenQASM3Serializer) – el serializador OpenQASM 3 del que deriva.
OpenQASM3Serializer
class qiskit.circuit.annotation.OpenQASM3Serializer
Bases: ABC
La interfaz para serializadores y deserializadores de Annotation objetos en OpenQASM e 3.
Para obtener más información sobre la compatibilidad con OpenQASM 3 en Qiskit, consulte qiskit.qasm3.
Esta clase de definición de interfaz está diseñada para ser subclasificada. OpenQASM 3 las anotaciones no tienen estado dentro de un programa, por lo tanto una subclase no debe rastrear el estado.
Ejemplos
Una pasada del transpilador de colección de bloques
Uno de los principales objetivos del marco de anotación es permitir que los análisis y comandos personalizados se almacenen en los circuitos de una manera local a la instrucción, ya sea por el usuario en la entrada al compilador, o para que una pasada del compilador almacene información para su consumo posterior.
Por ejemplo, podemos escribir una pasada sencilla del transpilador que recopile series de operaciones de un solo qubit, coloque cada serie en un BoxOp, calcule la acción unitaria total y la adjunte como una anotación personalizada, de modo que no sea necesario repetir el mismo análisis más adelante, incluso si se optimizan los componentes internos de cada bloque.
from qiskit.circuit import annotation, QuantumCircuit, BoxOp
from qiskit.quantum_info import Operator
from qiskit.transpiler import TransformationPass
class PerformsUnitary(annotation.Annotation):
namespace = "unitary"
def __init__(self, matrix):
self.matrix = matrix
class Collect1qRuns(TransformationPass):
def run(self, dag):
for run in dag.collect_1q_runs():
block = QuantumCircuit(1)
for node in run:
block.append(node.op, [0], [])
box = BoxOp(block, annotations=[PerformsUnitary(Operator(block).data)])
dag.replace_block_with_op(run, box, {run[0].qargs[0]: 0})
return dagPara serializar la anotación a OpenQASM 3, debemos definir una lógica personalizada, ya que el análisis en sí es totalmente personalizado. La serialización es independiente de la anotación; puede haber circunstancias en las que la serialización deba hacerse de otra manera.
import ast
import numpy as np
class Serializer(annotation.OpenQASM3Serializer):
def dump(self, annotation):
if annotation.namespace != "unitary":
return NotImplemented
line = lambda row: "[" + ", ".join(repr(x) for x in row) + "]"
return "[" + ", ".join(line(row) for row in annotation.matrix.tolist()) + "]"
def load(self, namespace, payload):
if namespace != "unitary":
return NotImplemented
return PerformsUnitary(np.array(ast.literal_eval(payload), dtype=complex))Por último, se puede juntar todo esto, mostrando la salida OpenQASM 3.
from qiskit import qasm3
qc = QuantumCircuit(3)
qc.s(0)
qc.t(0)
qc.y(1)
qc.x(1)
qc.h(2)
qc.s(2)
collected = Collect1qRuns()(qc)
handlers = {"unitary": Serializer()}
dumped = qasm3.dumps(collected, annotation_handlers=handlers)
print(dumped)OPENQASM 3.0;
include "stdgates.inc";
qubit[3] q;
@unitary[[(1+0j), 0j], [0j, (-0.7071067811865475+0.7071067811865475j)]]
box {
s q[0];
t q[0];
}
@unitary[[1j, 0j], [0j, -1j]]
box {
y q[1];
x q[1];
}
@unitary[[(0.7071067811865475+0j), (0.7071067811865475+0j)], [0.7071067811865475j, -0.7071067811865475j]]
box {
h q[2];
s q[2];
}