Construcción de circuitos en el SDK - Amazon Braket

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

Construcción de circuitos en el SDK

En esta sección se proporcionan ejemplos sobre cómo definir un circuito, ver las puertas disponibles, extender un circuito y ver las puertas compatibles con cada dispositivo. También contiene instrucciones sobre cómo realizar la asignación manual de qubits, indicar al compilador que ejecute los circuitos exactamente como se ha definido y construir circuitos ruidosos con un simulador de ruido.

También puedes trabajar al nivel del pulso en Braket para varias compuertas, con algunas QPUs. Para obtener más información, consulte Control de pulsos en Amazon Braket.

Puertas y circuitos

Las puertas y circuitos cuánticos se definen en la clase de braket.circuits del SDK de Python de Amazon Braket. Desde el SDK, puede crear una instancia de un nuevo objeto de circuito llamando a Circuit().

Ejemplo: Definir un circuito

El ejemplo comienza definiendo un circuito de muestra de cuatro qubits (etiquetados q0, q1, q2 y q3) que consisten en puertas Hadamard estándar de un solo qubit y puertas CNOT de dos qubits. Puede visualizar este circuito llamando a la función print, como se muestra en el siguiente ejemplo.

# Import the circuit module from braket.circuits import Circuit # Define circuit with 4 qubits my_circuit = Circuit().h(range(4)).cnot(control=0, target=2).cnot(control=1, target=3) print(my_circuit)
T : │ 0 │ 1 │ ┌───┐ q0 : ─┤ H ├───●───────── └───┘ │ ┌───┐ │ q1 : ─┤ H ├───┼─────●─── └───┘ │ │ ┌───┐ ┌─┴─┐ │ q2 : ─┤ H ├─┤ X ├───┼─── └───┘ └───┘ │ ┌───┐ ┌─┴─┐ q3 : ─┤ H ├───────┤ X ├─ └───┘ └───┘ T : │ 0 │ 1 │

Ejemplo: Definir un circuito parametrizado

En este ejemplo, definimos un circuito con puertas que dependen de parámetros libres. Podemos especificar los valores de estos parámetros para crear un circuito nuevo o, al enviar el circuito, para que se ejecute como una tarea cuántica en determinados dispositivos.

from braket.circuits import Circuit, FreeParameter # Define a FreeParameter to represent the angle of a gate alpha = FreeParameter("alpha") # Define a circuit with three qubits my_circuit = Circuit().h(range(3)).cnot(control=0, target=2).rx(0, alpha).rx(1, alpha) print(my_circuit)

Puede crear un circuito nuevo no parametrizado a partir de uno parametrizado proporcionando un único argumento float (que será el valor que tomarán todos los parámetros libres) o argumentos de palabra clave que especifiquen el valor de cada parámetro del circuito, como se indica a continuación.

my_fixed_circuit = my_circuit(1.2) my_fixed_circuit = my_circuit(alpha=1.2) print(my_fixed_circuit)

Tenga en cuenta que my_circuit no está modificado, por lo que puede usarlo para crear instancias de muchos circuitos nuevos con valores de parámetros fijos.

Ejemplo: Modificar las puertas de un circuito

En el siguiente ejemplo se define un circuito con puertas que utilizan modificadores de control y potencia. Puede utilizar estas modificaciones para crear nuevas puertas, como la puerta de Ry controlada.

from braket.circuits import Circuit # Create a bell circuit with a controlled x gate my_circuit = Circuit().h(0).x(control=0, target=1) # Add a multi-controlled Ry gate of angle .13 my_circuit.ry(angle=.13, target=2, control=(0, 1)) # Add a 1/5 root of X gate my_circuit.x(0, power=1/5) print(my_circuit)

Los modificadores de puerta solo son compatibles con el simulador local.

Ejemplo: Consultar todas las puertas disponibles

En el siguiente ejemplo se muestra cómo ver todas las puertas disponibles en Amazon Braket.

from braket.circuits import Gate # Print all available gates in Amazon Braket gate_set = [attr for attr in dir(Gate) if attr[0].isupper()] print(gate_set)

El resultado de este código muestra una lista de todas las puertas.

['CCNot', 'CNot', 'CPhaseShift', 'CPhaseShift00', 'CPhaseShift01', 'CPhaseShift10', 'CSwap', 'CV', 'CY', 'CZ', 'ECR', 'GPhase', 'GPi', 'GPi2', 'H', 'I', 'ISwap', 'MS', 'PRx', 'PSwap', 'PhaseShift', 'PulseGate', 'Rx', 'Ry', 'Rz', 'S', 'Si', 'Swap', 'T', 'Ti', 'U', 'Unitary', 'V', 'Vi', 'X', 'XX', 'XY', 'Y', 'YY', 'Z', 'ZZ']

Cualquiera de estas puertas se puede añadir a un circuito llamando al método correspondiente a ese tipo de circuito. Por ejemplo, llamar a circ.h(0) para añadir una puerta Hadamard a al primer qubit.

nota

Las puertas están colocadas en su sitio y en el siguiente ejemplo se añaden todas las puertas enumeradas en el ejemplo anterior al mismo circuito.

circ = Circuit() # toffoli gate with q0, q1 the control qubits and q2 the target. circ.ccnot(0, 1, 2) # cnot gate circ.cnot(0, 1) # controlled-phase gate that phases the |11> state, cphaseshift(phi) = diag((1,1,1,exp(1j*phi))), where phi=0.15 in the examples below circ.cphaseshift(0, 1, 0.15) # controlled-phase gate that phases the |00> state, cphaseshift00(phi) = diag([exp(1j*phi),1,1,1]) circ.cphaseshift00(0, 1, 0.15) # controlled-phase gate that phases the |01> state, cphaseshift01(phi) = diag([1,exp(1j*phi),1,1]) circ.cphaseshift01(0, 1, 0.15) # controlled-phase gate that phases the |10> state, cphaseshift10(phi) = diag([1,1,exp(1j*phi),1]) circ.cphaseshift10(0, 1, 0.15) # controlled swap gate circ.cswap(0, 1, 2) # swap gate circ.swap(0,1) # phaseshift(phi)= diag([1,exp(1j*phi)]) circ.phaseshift(0,0.15) # controlled Y gate circ.cy(0, 1) # controlled phase gate circ.cz(0, 1) # Echoed cross-resonance gate applied to q0, q1 circ = Circuit().ecr(0,1) # X rotation with angle 0.15 circ.rx(0, 0.15) # Y rotation with angle 0.15 circ.ry(0, 0.15) # Z rotation with angle 0.15 circ.rz(0, 0.15) # Hadamard gates applied to q0, q1, q2 circ.h(range(3)) # identity gates applied to q0, q1, q2 circ.i([0, 1, 2]) # iswap gate, iswap = [[1,0,0,0],[0,0,1j,0],[0,1j,0,0],[0,0,0,1]] circ.iswap(0, 1) # pswap gate, PSWAP(phi) = [[1,0,0,0],[0,0,exp(1j*phi),0],[0,exp(1j*phi),0,0],[0,0,0,1]] circ.pswap(0, 1, 0.15) # X gate applied to q1, q2 circ.x([1, 2]) # Y gate applied to q1, q2 circ.y([1, 2]) # Z gate applied to q1, q2 circ.z([1, 2]) # S gate applied to q0, q1, q2 circ.s([0, 1, 2]) # conjugate transpose of S gate applied to q0, q1 circ.si([0, 1]) # T gate applied to q0, q1 circ.t([0, 1]) # conjugate transpose of T gate applied to q0, q1 circ.ti([0, 1]) # square root of not gate applied to q0, q1, q2 circ.v([0, 1, 2]) # conjugate transpose of square root of not gate applied to q0, q1, q2 circ.vi([0, 1, 2]) # exp(-iXX theta/2) circ.xx(0, 1, 0.15) # exp(i(XX+YY) theta/4), where theta=0.15 in the examples below circ.xy(0, 1, 0.15) # exp(-iYY theta/2) circ.yy(0, 1, 0.15) # exp(-iZZ theta/2) circ.zz(0, 1, 0.15) # IonQ native gate GPi with angle 0.15 applied to q0 circ.gpi(0, 0.15) # IonQ native gate GPi2 with angle 0.15 applied to q0 circ.gpi2(0, 0.15) # IonQ native gate MS with angles 0.15, 0.15, 0.15 applied to q0, q1 circ.ms(0, 1, 0.15, 0.15, 0.15)

Además del conjunto de puertas predefinido, también puede aplicar puertas unitarias autodefinidas al circuito. Pueden ser puertas de un solo qubit (como se muestra en el siguiente código fuente) o puertas de varios qubit aplicadas a los qubits definidos por el parámetro targets.

import numpy as np # Apply a general unitary my_unitary = np.array([[0, 1],[1, 0]]) circ.unitary(matrix=my_unitary, targets=[0])

Ejemplo: Ampliar los circuitos existentes

Puede ampliar los circuitos existentes añadiendo instrucciones. Una Instruction es una directiva cuántica que describe la tarea cuántica que se debe realizar en un dispositivo cuántico. Los operadores de la Instruction incluyen objetos del tipo Gate únicamente.

# Import the Gate and Instruction modules from braket.circuits import Gate, Instruction # Add instructions directly. circ = Circuit([Instruction(Gate.H(), 4), Instruction(Gate.CNot(), [4, 5])]) # Or with add_instruction/add functions instr = Instruction(Gate.CNot(), [0, 1]) circ.add_instruction(instr) circ.add(instr) # Specify where the circuit is appended circ.add_instruction(instr, target=[3, 4]) circ.add_instruction(instr, target_mapping={0: 3, 1: 4}) # Print the instructions print(circ.instructions) # If there are multiple instructions, you can print them in a for loop for instr in circ.instructions: print(instr) # Instructions can be copied new_instr = instr.copy() # Appoint the instruction to target new_instr = instr.copy(target=[5, 6]) new_instr = instr.copy(target_mapping={0: 5, 1: 6})

Ejemplo: Ver las puertas que admite cada dispositivo

Los simuladores admiten todas las puertas del SDK de Braket, pero los dispositivos QPU admiten un subconjunto más pequeño. Puede encontrar las puertas compatibles de un dispositivo en las propiedades del dispositivo. A continuación se muestra un ejemplo con un dispositivo IonQ:

# Import the device module from braket.aws import AwsDevice device = AwsDevice("arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1") # Get device name device_name = device.name # Show supportedQuantumOperations (supported gates for a device) device_operations = device.properties.dict()['action']['braket.ir.openqasm.program']['supportedOperations'] print('Quantum Gates supported by {}:\n {}'.format(device_name, device_operations))
Quantum Gates supported by Aria 1: ['x', 'y', 'z', 'h', 's', 'si', 't', 'ti', 'v', 'vi', 'rx', 'ry', 'rz', 'cnot', 'swap', 'xx', 'yy', 'zz']

Es posible que las puertas compatibles deban compilarse en puertas nativas antes de que puedan ejecutarse en hardware cuántico. Cuando envía un circuito, Amazon Braket realiza esta compilación automáticamente.

Ejemplo: Recuperar mediante programación la fidelidad de las puertas nativas compatibles con un dispositivo

Puede ver la información de fidelidad en la página Dispositivos de la consola de Braket. A veces resulta útil acceder a la misma información de forma programática. El siguiente código muestra cómo extraer la fidelidad de las dos puertas de qubit de una QPU.

# Import the device module from braket.aws import AwsDevice device = AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-3") # Specify the qubits a=10 b=11 edge_properties_entry = device.properties.standardized.twoQubitProperties['10-11'].twoQubitGateFidelity gate_name = edge_properties_entry[0].gateName fidelity = edge_properties_entry[0].fidelity print(f"Fidelity of the {gate_name} gate between qubits {a} and {b}: {fidelity}")

Conjuntos de programas

Los conjuntos de programas ejecutan de manera eficiente varios circuitos cuánticos en una sola tarea cuántica. En esa tarea, puede enviar hasta 100 circuitos cuánticos o un solo circuito paramétrico con hasta 100 conjuntos de parámetros diferentes. Esta operación minimiza el tiempo entre las ejecuciones subsiguientes de los circuitos y reduce la sobrecarga de procesamiento de las tareas cuánticas. Actualmente, los conjuntos de programas son compatibles con Amazon Braket Local Simulator y otros AQT dispositivosIQM. Rigetti

Definir un ProgramSet

El siguiente primer ejemplo de código muestra cómo desarrollar un ProgramSet utilizando circuitos parametrizados y circuitos sin parámetros.

from braket.aws import AwsDevice from braket.circuits import Circuit, FreeParameter from braket.program_sets.circuit_binding import CircuitBinding from braket.program_sets import ProgramSet # Initialize the quantum device device = AwsDevice("arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet") # Define circuits circ1 = Circuit().h(0).cnot(0, 1) circ2 = Circuit().rx(0, 0.785).ry(1, 0.393).cnot(1, 0) circ3 = Circuit().t(0).t(1).cz(0, 1).s(0).cz(1, 2).s(1).s(2) parameterize_circuit = Circuit().rx(0, FreeParameter("alpha")).cnot(0, 1).ry(1, FreeParameter("beta")) # Create circuit bindings with different parameters circuit_binding = CircuitBinding( circuit=parameterize_circuit, input_sets={ 'alpha': (0.10, 0.11, 0.22, 0.34, 0.45), 'beta': (1.01, 1.01, 1.03, 1.04, 1.04), }) # Creating the program set program_set_1 = ProgramSet([ circ1, circ2, circ3, circuit_binding, ])

Este conjunto de programas contiene cuatro programas únicos: circ1, circ2, circ3 y circuit_binding. El programa circuit_binding se ejecuta con cinco vínculos de parámetros diferentes, lo que crea cinco ejecutables. Los otros tres programas sin parámetros crean un ejecutable cada uno. Esto da como resultado un total de ocho ejecutables, como se muestra en la imagen siguiente.

ProgramSet estructura con cuatro circuitos, donde c4 utiliza CircuitBinding para procesar cinco conjuntos de entradas.

El siguiente segundo ejemplo de código muestra cómo utilizar el método product() para adjuntar el mismo conjunto de observables a cada ejecutable del conjunto de programas.

from braket.circuits.observables import I, X, Y, Z observables = [Z(0) @ Z(1), X(0) @ X(1), Z(0) @ X(1), X(0) @ Z(1)] program_set_2 = ProgramSet.product( circuits=[circ1, circ2, circuit_binding], observables=observables )

En el caso de los programas sin parámetros, cada observable se mide para cada circuito. En el caso de los programas paramétricos, cada observable se mide para cada conjunto de entradas, como se muestra en la siguiente imagen.

ProgramSet.product muestra la ejecución paralela de tres circuitos, con c3 utilizando CircuitBinding para procesar cinco conjuntos de entradas con cinco observables cada uno.

En el tercer ejemplo de código siguiente se muestra cómo utilizar el método zip() para emparejar observables individuales con conjuntos de parámetros específicos en el ProgramSet.

program_set_3 = ProgramSet.zip( circuits=circuit_binding, observables=observables + [Y(0) @ Y(1)] )
ProgramSet.zip con una CircuitBinding demostración de cinco ejecuciones paralelas utilizando un circuito compartido con observables individuales por conjunto de entradas.

En lugar de CircuitBinding(), puede comprimir directamente una lista de observables con una lista de circuitos y conjuntos de entradas.

program_set_4 = ProgramSet.zip( circuits=[circ1, circ2, circ3], input_sets=[{}, {}, {}], observables=observables[:3] )
ProgramSet.zip que muestra la ejecución paralela de siete circuitos con su correspondiente conjunto de entradas individuales y observables individuales.

Para obtener más información y ejemplos de conjuntos de programas, consulta la carpeta de conjuntos de programas en Github. amazon-braket-examples

Inspeccionar y ejecutar un conjunto de programas en un dispositivo

El número de ejecutables de un conjunto de programas es igual al número de circuitos vinculados a parámetros únicos. Calcule el número total de shots y ejecutables de circuitos mediante el siguiente ejemplo de código.

# Number of shots per executable shots = 10 num_executables = program_set_1.total_executables # Calculate total number of shots across all executables total_num_shots = shots*num_executables
nota

Con los conjuntos de programas, se paga una tarifa única por tarea y una tarifa por shot basada en el número total de shots en todos los circuitos de un conjunto de programas.

Para ejecutar el conjunto de programas, use el siguiente ejemplo de código.

# Run the program set task = device.run( program_set_1, shots=total_num_shots, )

Al utilizar dispositivos Rigetti, su conjunto de programas puede permanecer en el estado RUNNING mientras las tareas estén parcialmente terminadas y parcialmente en cola. Para obtener resultados más rápidos, considere enviar su conjunto de programas como Trabajo híbrido.

Analizando los resultados

Ejecute el siguiente código para analizar y medir los resultados de los ejecutables en un ProgramSet.

# Get the results from a program set result = task.result() # Get the first executbable first_program = result[0] first_executable = first_program[0] # Inspect the results of the first executable measurements_from_first_executable = first_executable.measurements print(measurements_from_first_executable)

Medición parcial

En lugar de medir todos los qubits de un circuito cuántico, utilice la medición parcial para medir qubits individuales o un subconjunto de qubits.

nota

Las características adicionales, como la medición del circuito medio y las operaciones de prealimentación, están disponibles como capacidades experimentales. Consulte Acceso a circuitos dinámicos en dispositivos IQM.

Ejemplo: Medir un subconjunto de qubits

El siguiente ejemplo de código muestra una medición parcial midiendo solo qubit 0 en un circuito de estado de Bell.

from braket.devices import LocalSimulator from braket.circuits import Circuit # Use the local state vector simulator device = LocalSimulator() # Define an example bell circuit and measure qubit 0 circuit = Circuit().h(0).cnot(0, 1).measure(0) # Run the circuit task = device.run(circuit, shots=10) # Get the results result = task.result() # Print the circuit and measured qubits print(circuit) print() print("Measured qubits: ", result.measured_qubits)

Asignación manual de qubit

Cuando ejecuta un circuito cuántico en computadoras cuánticas desde Rigetti, puede utilizar opcionalmente la asignación manual de qubit para controlar qué qubits se utilizan para su algoritmo. La consola de Amazon Braket y el SDK de Amazon Braket le ayudan a inspeccionar los datos de calibración más recientes de la unidad de procesamiento cuántico (QPU) que haya seleccionado, de modo que pueda seleccionar los mejores qubits para su experimento.

La asignación manual de qubit le permite ejecutar los circuitos con mayor precisión e investigar las propiedades de qubit individuales. Los investigadores y usuarios avanzados optimizan el diseño de sus circuitos basándose en los últimos datos de calibración de dispositivos y pueden obtener resultados más precisos.

En el siguiente ejemplo se muestra cómo asignar qubits de forma explícita.

# Import the device module from braket.aws import AwsDevice device = AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-3") circ = Circuit().h(0).cnot(0, 7) # Indices of actual qubits in the QPU # Set up S3 bucket (where results are stored) my_bucket = "amazon-braket-s3-demo-bucket" # The name of the bucket my_prefix = "your-folder-name" # The name of the folder in the bucket s3_location = (my_bucket, my_prefix) my_task = device.run(circ, s3_location, shots=100, disable_qubit_rewiring=True)

Para obtener más información, consulte los ejemplos de Amazon Braket sobre GitHub, o más específicamente, este cuaderno: Asignación de qubits en dispositivos QPU.

Compilación verbatim

Cuando ejecuta un circuito cuántico en computadoras cuánticas basadas en puertas, puede indicar al compilador que ejecute sus circuitos exactamente como se ha definido sin ninguna modificación. Mediante la compilación verbatim, puede especificar que un circuito completo se conserve exactamente como se ha especificado o que solo se conserven partes específicas del mismo (solo compatible con Rigetti). Al desarrollar algoritmos para la evaluación comparativa de hardware o protocolos de mitigación de errores, es necesario tener la opción de especificar con exactitud las puertas y los diseños de circuitos que se ejecutan en el hardware. La compilación verbatim le permite controlar directamente el proceso de compilación desactivando determinados pasos de optimización, lo que garantiza que sus circuitos funcionen exactamente según lo diseñado.

La compilación literal es compatible con los Rigetti dispositivosAQT, IonQIQM, y requiere el uso de puertas nativas. Cuando utilice la compilación verbatim, es recomendable comprobar la topología del dispositivo para asegurarse de que las puertas se llaman sobre qubits conectados y que el circuito utiliza las puertas nativas compatibles con el hardware. En el siguiente ejemplo se muestra cómo acceder mediante programación a la lista de puertas nativas admitidas por un dispositivo.

device.properties.paradigm.nativeGateSet

Para Rigetti, el recableado de qubit debe desactivarse configurando disableQubitRewiring=True para el uso con la compilación verbatim. Si disableQubitRewiring=False se establece cuando se utilizan cuadros verbatim en una compilación, el circuito cuántico no pasa la validación y no se ejecuta.

Si la compilación verbatim se habilita para un circuito y se ejecuta en una QPU que no la admite, se genera un error que indica que una operación no compatible ha provocado el fallo de la tarea. A medida que más hardware cuántico admita de forma nativa las funciones del compilador, se ampliará esta característica para incluir estos dispositivos. Los dispositivos que admiten la compilación verbatim la incluyen como operación compatible cuando se consulta con el siguiente código.

from braket.aws import AwsDevice from braket.device_schema.device_action_properties import DeviceActionType device = AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-3") device.properties.action[DeviceActionType.OPENQASM].supportedPragmas

No hay ningún costo adicional asociado al uso de la compilación verbatim. Se le seguirán cobrando las tareas cuánticas ejecutadas en dispositivos QPU de Braket, instancias de cuaderno y simuladores bajo demanda según las tarifas actuales especificadas en la página de precios de Amazon Braket. Para obtener más información, consulte el cuaderno de ejemplos de compilación verbatim.

nota

Si utilizas OpenQASM para escribir los circuitos de los IonQ dispositivos AQT y, además, deseas mapear tu circuito directamente a los cúbits físicos, tendrás que usar las, #pragma braket verbatim ya que OpenQasm ignora la disableQubitRewiring bandera.

Simulación de ruido

Para crear una instancia de simulador de ruido local, puede cambiar el backend de la siguiente manera.

# Import the device module from braket.aws import AwsDevice device = LocalSimulator(backend="braket_dm")

Puede desarrollar circuitos ruidosos de dos maneras:

  1. Desarrolle el circuito ruidoso de abajo hacia arriba.

  2. Tome un circuito existente sin ruido e inyecte ruido en él.

En el siguiente ejemplo se muestran los enfoques utilizando un circuito básico con ruido despolarizante y un canal Kraus personalizado.

import scipy.stats import numpy as np # Bottom up approach # Apply depolarizing noise to qubit 0 with probability of 0.1 circ = Circuit().x(0).x(1).depolarizing(0, probability=0.1) # Create an arbitrary 2-qubit Kraus channel E0 = scipy.stats.unitary_group.rvs(4) * np.sqrt(0.8) E1 = scipy.stats.unitary_group.rvs(4) * np.sqrt(0.2) K = [E0, E1] # Apply a two-qubit Kraus channel to qubits 0 and 2 circ = circ.kraus([0, 2], K)
from braket.circuits import Noise # Inject noise approach # Define phase damping noise noise = Noise.PhaseDamping(gamma=0.1) # The noise channel is applied to all the X gates in the circuit circ = Circuit().x(0).y(1).cnot(0, 2).x(1).z(2) circ_noise = circ.copy() circ_noise.apply_gate_noise(noise, target_gates=Gate.X)

Ejecutar un circuito ofrece la misma experiencia de usuario que antes, como se muestra en los dos ejemplos siguientes.

Ejemplo 1

task = device.run(circ, shots=100)

O

Ejemplo 2

task = device.run(circ_noise, shots=100)

Para ver más ejemplos, consulte el ejemplo del simulador de ruido introductorio de Braket.