Hola AHS: ejecuta tu primera simulación hamiltoniana analógica - 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.

Hola AHS: ejecuta tu primera simulación hamiltoniana analógica

CENIZAS

La simulación hamiltoniana analógica (AHS) es un paradigma de la computación cuántica diferente de los circuitos cuánticos: en lugar de una secuencia de puertas, cada una de las cuales actúa solo sobre un par de cúbits a la vez, un programa AHS se define mediante los parámetros dependientes del tiempo y el espacio del hamiltoniano en cuestión. El hamiltoniano de un sistema codifica sus niveles de energía y los efectos de las fuerzas externas, que en conjunto gobiernan la evolución temporal de sus estados. Para sistemas de cúbits N, el hamiltoniano se puede representar mediante una matriz cuadrada de números complejos de 2 N X2 N.

Los dispositivos cuánticos capaces de ejecutar AHS ajustarán sus parámetros (por ejemplo, la amplitud y la desafinación de un campo impulsor coherente) para aproximarse de cerca a la evolución temporal del sistema cuántico según el hamiltoniano personalizado. El paradigma AHS es adecuado para simular propiedades estáticas y dinámicas de sistemas cuánticos de muchas partículas que interactúan. Las QPU especialmente diseñadas, como el dispositivo Aquila,QuEra pueden simular la evolución temporal de sistemas con tamaños que de otro modo serían inviables en el hardware clásico.

Cadena de espín que interactúa

Para ver un ejemplo canónico de un sistema de muchas partículas que interactúan, consideremos un anillo de ocho giros (cada uno de los cuales puede estar en los estados «arriba» ↑ ⟩ y «abajo»). Aunque es pequeño, este sistema modelo ya exhibe un puñado de fenómenos interesantes de materiales magnéticos naturales. En este ejemplo, mostraremos cómo preparar el llamado orden antiferromagnético, en el que los espines consecutivos apuntan en direcciones opuestas.


               AntiFerromagnetic

Arreglo

Utilizaremos un átomo neutro para representar cada giro, y los estados de espín «arriba» y «abajo» se codificarán en el estado excitado de Rydberg y en el estado fundamental de los átomos, respectivamente. En primer lugar, crearemos la disposición bidimensional. Podemos programar el anillo de giros anterior con el siguiente código.

Requisitos previos: Debe instalar primero el SDK de Braket. (Si utilizas una instancia de bloc de notas alojada en Braket, este SDK viene preinstalado con las libretas). Para reproducir los gráficos, también es necesario instalar matplotlib por separado con el comando shellpip install matplotlib.

import numpy as np import matplotlib.pyplot as plt # required for plotting from braket.ahs.atom_arrangement import AtomArrangement a = 5.7e-6 # nearest-neighbor separation (in meters) register = AtomArrangement() register.add(np.array([0.5, 0.5 + 1/np.sqrt(2)]) * a) register.add(np.array([0.5 + 1/np.sqrt(2), 0.5]) * a) register.add(np.array([0.5 + 1/np.sqrt(2), - 0.5]) * a) register.add(np.array([0.5, - 0.5 - 1/np.sqrt(2)]) * a) register.add(np.array([-0.5, - 0.5 - 1/np.sqrt(2)]) * a) register.add(np.array([-0.5 - 1/np.sqrt(2), - 0.5]) * a) register.add(np.array([-0.5 - 1/np.sqrt(2), 0.5]) * a) register.add(np.array([-0.5, 0.5 + 1/np.sqrt(2)]) * a)

con el que también podemos trazar

fig, ax = plt.subplots(1, 1, figsize=(7,7)) xs, ys = [register.coordinate_list(dim) for dim in (0, 1)] ax.plot(xs, ys, 'r.', ms=15) for idx, (x, y) in enumerate(zip(xs, ys)): ax.text(x, y, f" {idx}", fontsize=12) plt.show() # this will show the plot below in an ipython or jupyter session

               PlotNeutralAtoms

Interacción

Para preparar la fase antiferromagnética, necesitamos inducir interacciones entre los espines vecinos. Para ello utilizamos la interacción de van der Waals, que los dispositivos de átomos neutros implementan de forma nativa (como elAquila dispositivo deQuEra). Usando la representación del espín, el término hamiltoniano para esta interacción se puede expresar como una suma de todos los pares de espines (j, k).


               Interacción

En este caso,j nj=↑ ↑ esj un operador que toma el valor de 1 solo si el spin j está en estado «activo» y 0 en caso contrario. La fuerza es Vj,k =C6/(dj,k​) 6, donde C6 es el coeficiente fijo y dj,k es la distancia euclidiana entre los espines j y k. El efecto inmediato de este término de interacción es que cualquier estado en el que tanto el espín j como el espín k estén «arriba» tiene una energía elevada (en la cantidad Vj,k). Al diseñar cuidadosamente el resto del programa AHS, esta interacción evitará que los giros vecinos estén en estado «activo», un efecto comúnmente conocido como «bloqueo de Rydberg».

Campo de conducción

Al principio del programa AHS, todos los giros (por defecto) comienzan en su estado «inactivo», se encuentran en la denominada fase ferromagnética. Siguiendo con nuestro objetivo de preparar la fase antiferromagnética, especificamos un campo impulsor coherente dependiente del tiempo que hace que los giros pasen suavemente de este estado a un estado de muchos cuerpos, en el que se prefieren los estados «ascendentes». El hamiltoniano correspondiente se puede escribir como


               Unidad H

donde Ω (t), ω (t) y Δ (t) son la amplitud global (también conocida como frecuencia de Rabi), la fase y la desafinación del campo impulsor que afectan a todos los giros de manera uniforme. Aquí S−,k =↓k​k y S+,k =( S−,k) =↑ ↑k​ ~↓ sonk los operadores de descenso y aumento del spin k, respectivamente, yk nk =↑ ↑k es el mismo operador que antes. La parte Ω del campo impulsor acopla de manera coherente los estados «abajo» y «arriba» de todos los giros simultáneamente, mientras que la parte Δ controla la recompensa de energía en los estados «arriba».

Para programar una transición suave de la fase ferromagnética a la fase antiferromagnética, especificamos el campo impulsor con el siguiente código.

from braket.timings.time_series import TimeSeries from braket.ahs.driving_field import DrivingField # smooth transition from "down" to "up" state time_max = 4e-6 # seconds time_ramp = 1e-7 # seconds omega_max = 6300000.0 # rad / sec delta_start = -5 * omega_max delta_end = 5 * omega_max omega = TimeSeries() omega.put(0.0, 0.0) omega.put(time_ramp, omega_max) omega.put(time_max - time_ramp, omega_max) omega.put(time_max, 0.0) delta = TimeSeries() delta.put(0.0, delta_start) delta.put(time_ramp, delta_start) delta.put(time_max - time_ramp, delta_end) delta.put(time_max, delta_end) phi = TimeSeries().put(0.0, 0.0).put(time_max, 0.0) drive = DrivingField( amplitude=omega, phase=phi, detuning=delta )

Podemos visualizar la serie temporal del campo conductor con el siguiente script.

fig, axes = plt.subplots(3, 1, figsize=(12, 7), sharex=True) ax = axes[0] time_series = drive.amplitude.time_series ax.plot(time_series.times(), time_series.values(), '.-'); ax.grid() ax.set_ylabel('Omega [rad/s]') ax = axes[1] time_series = drive.detuning.time_series ax.plot(time_series.times(), time_series.values(), '.-'); ax.grid() ax.set_ylabel('Delta [rad/s]') ax = axes[2] time_series = drive.phase.time_series # Note: time series of phase is understood as a piecewise constant function ax.step(time_series.times(), time_series.values(), '.-', where='post'); ax.set_ylabel('phi [rad]') ax.grid() ax.set_xlabel('time [s]') plt.show() # this will show the plot below in an ipython or jupyter session

               DrivingTimeSeries

Programa AHS

El registro, el campo de conducción (y las interacciones implícitas de van der Waals) constituyen el programa de simulación hamiltoniana analógicaahs_program.

from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation ahs_program = AnalogHamiltonianSimulation( register=register, hamiltonian=drive )

Se ejecuta en un simulador local

Como este ejemplo es pequeño (menos de 15 giros), antes de ejecutarlo en una QPU compatible con AHS, podemos ejecutarlo en el simulador AHS local que viene con el SDK de Braket. Dado que el simulador local está disponible de forma gratuita con el SDK de Braket, se trata de una buena práctica para garantizar que nuestro código se ejecute correctamente.

Aquí podemos establecer el número de disparos en un valor alto (por ejemplo, 1 millón) porque el simulador local rastrea la evolución temporal del estado cuántico y extrae muestras del estado final; por lo tanto, aumenta el número de disparos y aumenta el tiempo de ejecución total solo marginalmente.

from braket.devices import LocalSimulator device = LocalSimulator("braket_ahs") result_simulator = device.run( ahs_program, shots=1_000_000 ).result() # takes about 5 seconds

Análisis de resultados del simulador

Podemos sumar los resultados de los tiros con la siguiente función, que deduce el estado de cada giro (que puede ser «d» para «abajo», «u» para «arriba» o «e» para un sitio vacío) y cuenta cuántas veces se produjo cada configuración en los tiros.

from collections import Counter def get_counts(result): """Aggregate state counts from AHS shot results A count of strings (of length = # of spins) are returned, where each character denotes the state of a spin (site): e: empty site u: up state spin d: down state spin Args: result (braket.tasks.analog_hamiltonian_simulation_quantum_task_result.AnalogHamiltonianSimulationQuantumTaskResult) Returns dict: number of times each state configuration is measured """ state_counts = Counter() states = ['e', 'u', 'd'] for shot in result.measurements: pre = shot.pre_sequence post = shot.post_sequence state_idx = np.array(pre) * (1 + np.array(post)) state = "".join(map(lambda s_idx: states[s_idx], state_idx)) state_counts.update((state,)) return dict(state_counts) counts_simulator = get_counts(result_simulator) # takes about 5 seconds print(counts_simulator)
{'udududud': 330944, 'dudududu': 329576, 'dududdud': 38033, ...}

Estecounts es un diccionario que cuenta el número de veces que se observa cada configuración de estado en las tomas. También podemos visualizarlos con el siguiente código.

from collections import Counter def has_neighboring_up_states(state): if 'uu' in state: return True if state[0] == 'u' and state[-1] == 'u': return True return False def number_of_up_states(state): return Counter(state)['u'] def plot_counts(counts): non_blockaded = [] blockaded = [] for state, count in counts.items(): if not has_neighboring_up_states(state): collection = non_blockaded else: collection = blockaded collection.append((state, count, number_of_up_states(state))) blockaded.sort(key=lambda _: _[1], reverse=True) non_blockaded.sort(key=lambda _: _[1], reverse=True) for configurations, name in zip((non_blockaded, blockaded), ('no neighboring "up" states', 'some neighboring "up" states')): plt.figure(figsize=(14, 3)) plt.bar(range(len(configurations)), [item[1] for item in configurations]) plt.xticks(range(len(configurations))) plt.gca().set_xticklabels([item[0] for item in configurations], rotation=90) plt.ylabel('shots') plt.grid(axis='y') plt.title(f'{name} configurations') plt.show() plot_counts(counts_simulator)

               Cuenta AHS 1

               Cuenta AHS 2

De los gráficos, podemos leer las siguientes observaciones para comprobar que hemos preparado satisfactoriamente la fase antiferromagnética.

  1. En general, los estados no bloqueados (en los que no hay dos giros vecinos en el estado «activo») son más comunes que los estados en los que al menos un par de giros vecinos están ambos en estados «activos».

  2. En general, se prefieren los estados con más excitaciones «activas», a menos que la configuración esté bloqueada.

  3. Los estados más comunes son de hecho los estados antiferromagnéticos perfectos"dudududu" y"udududud".

  4. Los segundos estados más comunes son aquellos en los que solo hay 3 excitaciones «ascendentes» con separaciones consecutivas de 1, 2, 2. Esto demuestra que la interacción de van der Waals también tiene un efecto (aunque mucho menor) en los vecinos más cercanos.

QPU QuEra Aquila de Running on

Requisitos previos: Además de instalar pip el SDK de Braket, si es la primera vez que utiliza Amazon Braket, asegúrese de haber completado los pasos de introducción necesarios.

nota

Si utilizas una instancia de notebook alojada en Braket, el SDK de Braket viene preinstalado con la instancia.

Con todas las dependencias instaladas, podemos conectarnos a laAquila QPU.

from braket.aws import AwsDevice aquila_qpu = AwsDevice("arn:aws:braket:us-east-1::device/qpu/quera/Aquila")

Para que nuestro programa AHS sea adecuado para laQuEra máquina, necesitamos redondear todos los valores para cumplir con los niveles de precisión permitidos por laAquila QPU. (Estos requisitos se rigen por los parámetros del dispositivo con la palabra «Resolución» en su nombre. Podemos verlas ejecutándolasaquila_qpu.properties.dict() en un cuaderno. Para obtener más información sobre las capacidades y los requisitos de Aquila, consulte el cuaderno Introducción a Aquila.) Podemos hacerlo llamando aldiscretize método.

discretized_ahs_program = ahs_program.discretize(aquila_qpu)

Ahora podemos ejecutar el programa (por ahora solo se ejecutan 100 disparos) en laAquila QPU.

nota

La ejecución de este programa en elAquila procesador implicará un coste. El SDK de Amazon Braket incluye un rastreador de costos que permite a los clientes establecer límites de costos y realizar un seguimiento de sus costos casi en tiempo real.

task = aquila_qpu.run(discretized_ahs_program, shots=100) metadata = task.metadata() task_arn = metadata['quantumTaskArn'] task_status = metadata['status'] print(f"ARN: {task_arn}") print(f"status: {task_status}")
task ARN: arn:aws:braket:us-east-1:123456789012:quantum-task/12345678-90ab-cdef-1234-567890abcdef task status: CREATED

Debido a la gran diferencia en cuanto al tiempo que puede tardar en ejecutarse una tarea (según la disponibilidad, las ventanas y el uso de la QPU), es una buena idea anotar el ARN de la tarea para poder comprobar su estado más adelante con el siguiente fragmento de código.

# Optionally, in a new python session from braket.aws import AwsQuantumTask SAVED_TASK_ARN = "arn:aws:braket:us-east-1:123456789012:quantum-task/12345678-90ab-cdef-1234-567890abcdef" task = AwsQuantumTask(arn=SAVED_TASK_ARN) metadata = task.metadata() task_arn = metadata['quantumTaskArn'] task_status = metadata['status'] print(f"ARN: {task_arn}") print(f"status: {task_status}")
*[Output]* task ARN: arn:aws:braket:us-east-1:123456789012:quantum-task/12345678-90ab-cdef-1234-567890abcdef task status: COMPLETED

Una vez que se complete el estado (lo que también se puede comprobar desde la página de tareas de la consola de Amazon Braket), podemos consultar los resultados con:

result_aquila = task.result()

Análisis de resultados de QPU

Usando las mismasget_counts funciones que antes, podemos calcular los recuentos:

counts_aquila = get_counts(result_aquila) print(counts_aquila)
*[Output]* {'udududud': 24, 'dudududu': 17, 'dududdud': 3, ...}

y trácalos conplot_counts:

plot_counts(counts_aquila)

               QPUPlotCounts

Tenga en cuenta que una pequeña parte de las fotos tienen sitios vacíos (marcados con una «e»). Esto se debe a unas imperfecciones de laAquila QPU en la preparación del 1 al 2% por átomo. Además, los resultados coinciden con la simulación dentro de la fluctuación estadística esperada debido al reducido número de disparos.

Siguiente

Enhorabuena, ya ha ejecutado su primera carga de trabajo de AHS en Amazon Braket mediante el simulador AHS local y laAquila QPU.

Para obtener más información sobre la física de Rydberg, la simulación hamiltoniana analógica y elAquila dispositivo, consulte nuestros cuadernos de ejemplos.