

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: ejecución de la primera simulación hamiltoniana analógica
<a name="braket-get-started-hello-ahs"></a>

En esta sección se proporciona información sobre cómo ejecutar su primera simulación hamiltoniana analógica.

**Topics**
+ [Cadena de espín interactiva](#braket-get-started-interacting-spin-chain)
+ [Disposición](#braket-get-started-arrangement)
+ [Interacción](#braket-get-started-interaction)
+ [Campo de accionamiento](#braket-get-started-driving-field)
+ [Programa de AHS](#braket-get-started-ahs-program)
+ [Ejecución en un simulador local](#braket-get-started-running-local-simulator)
+ [Análisis de resultados de simuladores](#braket-get-started-analyzing-simulator-results)
+ [Se ejecuta en la QuEra QPU Aquila](#braket-get-started-running-aquila-qpu)
+ [Análisis de los resultados de la QPU](#braket-get-started-analyzing-qpu-results)
+ [Siguientes pasos](#braket-get-started-ahs-next)

## Cadena de espín interactiva
<a name="braket-get-started-interacting-spin-chain"></a>

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

![\[Diagrama que conecta 8 nodos circulares que contienen flechas inversas hacia arriba y hacia abajo.\]](http://docs.aws.amazon.com/es_es/braket/latest/developerguide/images/AntiFerromagnetic.png)


## Disposición
<a name="braket-get-started-arrangement"></a>

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

 **Requisitos previos**: es necesario instalar el [SDK de Braket](https://github.com/aws/amazon-braket-sdk-python#installing-the-amazon-braket-python-sdk). (Si utiliza una instancia de cuaderno alojada en Braket, este SDK viene preinstalado con los cuadernos). Para reproducir los gráficos, también debe instalar matplotlib por separado con el intérprete de comandos `pip install matplotlib`.

```
from braket.ahs.atom_arrangement import AtomArrangement
import numpy as np
import matplotlib.pyplot as plt  # Required for plotting

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)
```

que también podemos representar gráficamente con:

```
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
```

![\[Gráfico de dispersión que muestra puntos distribuidos entre valores positivos y negativos en ambos ejes.\]](http://docs.aws.amazon.com/es_es/braket/latest/developerguide/images/PlotNeutralAtoms.png)


## Interacción
<a name="braket-get-started-interaction"></a>

Para preparar la fase antiferromagnética, necesitamos inducir interacciones entre espines vecinos. Para ello utilizamos la [interacción de van der Waals](https://en.wikipedia.org/wiki/Van_der_Waals_force), que se implementa de forma nativa mediante dispositivos de átomos neutros (como el dispositivo Aquila de QuEra). Utilizando la representación de espines, el término hamiltoniano para esta interacción puede expresarse como una suma de todos los pares de espines (j, k).

![\[Ecuación de interacción hamiltoniana que muestra esta interacción expresada como una suma de todos los pares de espines (j, k).\]](http://docs.aws.amazon.com/es_es/braket/latest/developerguide/images/HInteraction.png)


Aquí, nj​=∣↑j​⟩⟨↑j​∣es un operador que toma el valor 1 solo si el espín j está en el estado «arriba», 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» tienen una energía elevada (con la cantidad Vj,k​). Al diseñar cuidadosamente el resto del programa de AHS, esta interacción evitará que los espines vecinos se encuentren ambos en el estado «arriba», un efecto conocido comúnmente como «bloqueo de Rydberg».

## Campo de accionamiento
<a name="braket-get-started-driving-field"></a>

Al inicio del programa de AHS, todos los espines (por defecto) comienzan en su estado «abajo», es decir, se encuentran en la denominada fase ferromagnética. Con la mirada puesta en nuestro objetivo de preparar la fase antiferromagnética, especificamos un campo de excitación coherente dependiente del tiempo que hace que los espines pasen suavemente de este estado a un estado de muchos cuerpos en el que se prefieren los estados «arriba». El hamiltoniano correspondiente se puede escribir como:

![\[Ecuación matemática que representa el cálculo de una función de accionamiento hamiltoniano.\]](http://docs.aws.amazon.com/es_es/braket/latest/developerguide/images/HDrive.png)


donde Ω(t),ϕ(t),Δ(t) son la amplitud global dependiente del tiempo (también conocida como [frecuencia de Rabi](https://en.wikipedia.org/wiki/Rabi_frequency)), la fase y la desintonización del campo de accionamiento que afecta a todos los espines de manera uniforme. Aquí, S−,k​=∣↓k​⟩⟨↑k​∣and S\$1,k​​=(S−,k​)†=∣↑k​⟩⟨↓k​∣son los operadores de bajada y subida del espín k, respectivamente, y nk​=∣↑k​⟩⟨↑k​∣es el mismo operador que antes. La parte Ω del campo de accionamiento acopla de forma coherente los estados «abajo» y «arriba» de todos los espines simultáneamente, mientras que la parte Δ controla la recompensa energética para los estados «arriba».

Para programar una transición suave de la fase ferromagnética a la fase antiferromagnética, especificamos el campo de excitación 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 de accionamiento 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
```

![\[Tres gráficos que muestran phi, delta y omega a lo largo del tiempo. La subgráfica superior muestra el crecimiento justo por encima de 6, rads/s donde permanece durante 4 segundos hasta que vuelve a caer a 0. El subgráfico del medio muestra el crecimiento lineal asociado de la derivada, y el subgráfico inferior ilustra una línea plana cercana a cero.\]](http://docs.aws.amazon.com/es_es/braket/latest/developerguide/images/DrivingTimeSeries.png)


## Programa de AHS
<a name="braket-get-started-ahs-program"></a>

El registro, el campo de accionamiento (y las interacciones implícitas de van der Waals) conforman el programa de simulación hamiltoniana analógica `ahs_program`.

```
from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation

ahs_program = AnalogHamiltonianSimulation(
   register=register,
   hamiltonian=drive
)
```

## Ejecución en un simulador local
<a name="braket-get-started-running-local-simulator"></a>

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

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

```
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 de simuladores
<a name="braket-get-started-analyzing-simulator-results"></a>

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

```
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)
```

```
*[Output]*
{'dddddddd': 5, 'dddddddu': 12, 'ddddddud': 15, ...}
```

Este `counts` es un diccionario que cuenta el número de veces que se observa cada configuración de estado en los shots. 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)
```

![\[Gráfico de barras que muestra un gran número de shots sin configuraciones de estados «arriba» adyacentes.\]](http://docs.aws.amazon.com/es_es/braket/latest/developerguide/images/AHSCounts1.png)


![\[Gráfico de barras que muestra los shots de algunas configuraciones de estados «arriba» vecinos, con 4 estados a 1,0 shots.\]](http://docs.aws.amazon.com/es_es/braket/latest/developerguide/images/AHSCounts2.png)


A partir de los gráficos, podemos extraer las siguientes observaciones que confirman que hemos preparado correctamente la fase antiferromagnética.

1. Por lo general, los estados no bloqueados (en los que no hay dos espines vecinos en estado «arriba») son más comunes que los estados en los que al menos un par de espines vecinos se encuentran ambos en estado «arriba».

1. Normalmente, se prefieren los estados con más excitaciones «arriba», a menos que la configuración esté bloqueada.

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

1. Los segundos estados más comunes son aquellos en los que solo hay tres excitaciones «arriba» con separaciones consecutivas de 1, 2, 2. Esto demuestra que la interacción de van der Waals también afecta (aunque en menor medida) a los vecinos más cercanos.

## Se ejecuta en la QuEra QPU Aquila
<a name="braket-get-started-running-aquila-qpu"></a>

 **Requisitos previos**: además de instalar el [SDK](https://github.com/aws/amazon-braket-sdk-python#installing-the-amazon-braket-python-sdk) de Braket con pip, si es nuevo en Amazon Braket, asegúrese de haber completado los [pasos de introducción](https://docs.aws.amazon.com/braket/latest/developerguide/braket-get-started.html).

**nota**  
Si utiliza una instancia de cuaderno alojada en Braket, el SDK de Braket viene preinstalado con la instancia.

Con todas las dependencias instaladas, podemos conectarnos a la QPU de Aquila.

```
from braket.aws import AwsDevice

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

Para que nuestro programa de AHS sea adecuado para la máquina de QuEra, necesitamos redondear todos los valores para que cumplan con los niveles de precisión permitidos por la QPU de Aquila. (Estos requisitos se rigen por los parámetros del dispositivo con «Resolución» en su nombre. Podemos verlos al ejecutar `aquila_qpu.properties.dict()` en un cuaderno. Para obtener más detalles sobre las capacidades y requisitos de Aquila, consulte el cuaderno [Introducción a Aquila](https://github.com/aws/amazon-braket-examples/blob/main/examples/analog_hamiltonian_simulation/01_Introduction_to_Aquila.ipynb)). Podemos hacerlo llamando al método `discretize`.

```
discretized_ahs_program = ahs_program.discretize(aquila_qpu)
```

Ahora podemos ejecutar el programa (ejecutando solo 100 shots de momento) en la QPU de Aquila.

**nota**  
La ejecución de este programa en el procesador Aquila tendrá un costo. El SDK de Amazon Braket incluye un [Rastreador de costos](https://aws.amazon.com/blogs/quantum-computing/managing-the-cost-of-your-experiments-in-amazon-braket/) que permite a los clientes establecer límites de costos y realizar el 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}")
```

```
*[Output]*
ARN: arn:aws:braket:us-east-1:123456789012:quantum-task/12345678-90ab-cdef-1234-567890abcdef
status: CREATED
```

Debido a la gran variación en el tiempo que puede tardar en ejecutarse una tarea cuántica (dependiendo de los periodos de disponibilidad y la utilización de la QPU), es recomendable anotar el ARN de la tarea cuántica, 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]*
ARN: arn:aws:braket:us-east-1:123456789012:quantum-task/12345678-90ab-cdef-1234-567890abcdef
status: COMPLETED
```

Una vez que el estado sea COMPLETADO (lo cual también se puede comprobar desde la página de tareas cuánticas de la [consola](https://us-east-1.console.aws.amazon.com/braket/home?region=us-east-1#/tasks) de Amazon Braket), podemos consultar los resultados con:

```
result_aquila = task.result()
```

## Análisis de los resultados de la QPU
<a name="braket-get-started-analyzing-qpu-results"></a>

Usando las mismas funciones `get_counts` que antes, podemos computar los recuentos:

```
counts_aquila = get_counts(result_aquila)
   print(counts_aquila)
```

```
*[Output]*
{'dddududd': 2, 'dudududu': 18, 'ddududud': 4, ...}
```

y representarlos gráficamente con `plot_counts`:

```
plot_counts(counts_aquila)
```

![\[Gráfico de barras que muestra un gran número de shots sin configuraciones de estados «arriba» adyacentes.\]](http://docs.aws.amazon.com/es_es/braket/latest/developerguide/images/QPUPlotCounts1.png)


![\[Gráfico de barras que muestra los shots de algunas configuraciones de estados «arriba» vecinos, con 4 estados a 1,0 shots.\]](http://docs.aws.amazon.com/es_es/braket/latest/developerguide/images/QPUPlotCounts2.png)


Tenga en cuenta que una pequeña fracción de los shots tiene sitios vacíos (marcados con una «e»). Esto se debe a que la QPU de Aquila presenta imperfecciones en la preparación de un 1-2 % por átomo. Además, los resultados coinciden con los de la simulación dentro de la fluctuación estadística esperada debido al reducido número de shots.

## Siguientes pasos
<a name="braket-get-started-ahs-next"></a>

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

Para obtener más información sobre la física de Rydberg, la simulación hamiltoniana analógica y el dispositivo Aquila, consulte nuestros [cuadernos de ejemplos](https://github.com/aws/amazon-braket-examples/tree/main/examples/analog_hamiltonian_simulation).