OláAHS: Execute sua primeira simulação hamiltoniana analógica - Amazon Braket

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

OláAHS: Execute sua primeira simulação hamiltoniana analógica

Esta seção fornece informações sobre como executar sua primeira simulação hamiltoniana analógica.

Cadeia de rotação interativa

Para um exemplo canônico de um sistema de muitas partículas interagindo, vamos considerar um anel de oito giros (cada um dos quais pode estar nos estados “para cima” ⟩ e “para baixo” ↓⟩). Embora pequeno, esse sistema modelo já exibe um punhado de fenômenos interessantes de materiais magnéticos que ocorrem naturalmente. Neste exemplo, mostraremos como preparar a chamada ordem antiferromagnética, em que giros consecutivos apontam em direções opostas.

Diagrama conectando 8 nós circulares que contêm setas invertidas para cima e para baixo.

Arranjo

Usaremos um átomo neutro para representar cada spin, e os estados de spin “para cima” e “para baixo” serão codificados no estado excitado de Rydberg e no estado fundamental dos átomos, respectivamente. Primeiro, criamos o arranjo 2D. Podemos programar o anel de giros acima com o código a seguir.

Pré-requisitos: Você precisa instalar o Braket. SDK (Se você estiver usando uma instância de notebook hospedada no Braket, ela SDK vem pré-instalada com os notebooks.) Para reproduzir os gráficos, você também precisa instalar separadamente o matplotlib com o comando shell. pip 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)

com o qual também podemos traçar

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 dispersão mostrando pontos distribuídos entre valores positivos e negativos em ambos os eixos.

Interação

Para preparar a fase antiferromagnética, precisamos induzir interações entre spins vizinhos. Usamos a interação van der Waals para isso, que é implementada nativamente por dispositivos de átomos neutros (como o Aquila dispositivo de QuEra). Usando a representação de spin, o termo hamiltoniano para essa interação pode ser expresso como uma soma de todos os pares de spin (j, k).

Equação de interação hamiltoniana mostrando essa interação expressa como uma soma de todos os pares de spin (j, k).

Aqui, nj=↑ j ⟨↑ j é um operador que assume o valor de 1 somente se o spin j estiver no estado “ativo” e 0 caso contrário. A força é V j,k =C6/(dj,k​) 6, onde C 6 é o coeficiente fixo e d j,k é a distância euclidiana entre os spins j e k. O efeito imediato desse termo de interação é que qualquer estado em que o spin j e o spin k estejam “para cima” tem energia elevada (na quantidade Vj,k). Ao projetar cuidadosamente o resto do AHS programa, essa interação evitará que as rodadas vizinhas fiquem no estado “ativo”, um efeito comumente conhecido como “bloqueio de Rydberg”.

Campo de condução

No início do AHS programa, todos os giros (por padrão) começam em seu estado “inativo”, eles estão na chamada fase ferromagnética. De olho em nosso objetivo de preparar a fase antiferromagnética, especificamos um campo de condução coerente dependente do tempo que faz a transição suave dos giros desse estado para um estado de muitos corpos, onde os estados “ascendentes” são preferidos. O hamiltoniano correspondente pode ser escrito como

Equação matemática que descreve o cálculo de uma função de acionamento hamiltoniana.

onde Ω (t), θ (t), Δ (t) são a amplitude global dependente do tempo (também conhecida como frequência Rabi), a fase e o desajuste do campo de direção, afetando todos os giros de maneira uniforme. Aqui, S −,k =↓ k​ ⟨↑ k e S +,k =( S−,k) =↑ k​ ⟨↓ ․ são os operadores de abaixamento e elevação do spin k, respectivamente, e n =↑ k ⟨↑ ⟨↑ ․ é o mesmo operador de antes. k k k A parte Ω do campo de condução acopla coerentemente os estados “para baixo” e “para cima” de todos os giros simultaneamente, enquanto a parte Δ controla a recompensa de energia para os estados “para cima”.

Para programar uma transição suave da fase ferromagnética para a fase antiferromagnética, especificamos o campo de condução com o código a seguir.

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 a série temporal do campo de condução com o seguinte 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
Três gráficos mostrando phi, delta e ômega ao longo do tempo. O subgráfico superior mostra o crescimento um pouco acima de 6 rads/s, onde permanece por 4 segundos até cair de volta para 0. O subgráfico central mostra o crescimento linear associado da derivada, e o subgráfico inferior ilustra uma linha plana próxima de zero.

AHSprograma

O registro, o campo motriz (e as interações implícitas de van der Waals) compõem o programa de simulação hamiltoniana analógica. ahs_program

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

Executando no simulador local

Como este exemplo é pequeno (menos de 15 rodadas), antes de executá-lo em um AHS compatívelQPU, podemos executá-lo no AHS simulador local que vem com o Braket. SDK Como o simulador local está disponível gratuitamente com o BraketSDK, essa é a melhor prática para garantir que nosso código possa ser executado corretamente.

Aqui, podemos definir o número de fotos para um valor alto (digamos, 1 milhão) porque o simulador local rastreia a evolução temporal do estado quântico e extrai amostras do estado final; portanto, aumenta o número de fotos e aumenta o tempo de execução total apenas 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

Analisando os resultados do simulador

Podemos agregar os resultados da captura com a seguinte função que infere o estado de cada rotação (que pode ser “d” para “para baixo”, “u” para “para cima” ou “e” para o local vazio) e conta quantas vezes cada configuração ocorreu nas fotos.

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, ...}

Aqui counts está um dicionário que conta o número de vezes que cada configuração de estado é observada nas fotos. Também podemos visualizá-los com o código a seguir.

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 mostrando um grande número de fotos sem configurações vizinhas de estados “ativos”.
Gráfico de barras mostrando fotos de algumas configurações de estados vizinhos “ativos”, com estados descendo de valores altos para baixos.

A partir dos gráficos, podemos ler as seguintes observações para verificar se preparamos com sucesso a fase antiferromagnética.

  1. Geralmente, estados sem bloqueio (onde não há dois giros vizinhos no estado “ativo”) são mais comuns do que estados em que pelo menos um par de giros vizinhos está no estado “ativo”.

  2. Geralmente, estados com mais excitações “ascendentes” são favorecidos, a menos que a configuração esteja bloqueada.

  3. Os estados mais comuns são, de fato, os estados antiferromagnéticos perfeitos e. "dudududu" "udududud"

  4. Os segundos estados mais comuns são aqueles em que há apenas 3 excitações “ascendentes” com separações consecutivas de 1, 2, 2. Isso mostra que a interação de van der Waals também afeta (embora muito menor) os vizinhos mais próximos.

Correndo em QuEra Aquila QPU

Pré-requisitos: Além da instalação rápida do Braket SDK, se você for novo no Amazon Braket, certifique-se de ter concluído as etapas de introdução necessárias.

nota

Se você estiver usando uma instância de notebook hospedada no Braket, o Braket SDK vem pré-instalado com a instância.

Com todas as dependências instaladas, podemos nos conectar ao Aquila QPU.

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

Para tornar nosso AHS programa adequado para o QuEra máquina, precisamos arredondar todos os valores para cumprir os níveis de precisão permitidos pelo Aquila QPU. (Esses requisitos são regidos pelos parâmetros do dispositivo com “Resolução” no nome. Podemos vê-los executando aquila_qpu.properties.dict() em um notebook. Para obter mais detalhes sobre os recursos e requisitos do Aquila, consulte a Introdução ao notebook Aquila.) Podemos fazer isso chamando o discretize método.

discretized_ahs_program = ahs_program.discretize(aquila_qpu)

Agora podemos executar o programa (executando apenas 100 fotos por enquanto) no Aquila QPU.

nota

Executando este programa no Aquila o processador incorrerá em um custo. O Amazon SDK Braket inclui um rastreador de custos que permite aos clientes definir limites de custo e monitorar seus custos quase em tempo 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

Devido à grande variação de quanto tempo uma tarefa quântica pode levar para ser executada (dependendo das janelas de disponibilidade e da QPU utilização), é uma boa ideia anotar a tarefa quânticaARN, para que possamos verificar seu status posteriormente com o seguinte trecho 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

Quando o status for COMPLETED (que também pode ser verificado na página de tarefas quânticas do console Amazon Braket), podemos consultar os resultados com:

result_aquila = task.result()

Analisando QPU resultados

Usando as mesmas get_counts funções de antes, podemos calcular as contagens:

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

e plote-os complot_counts:

plot_counts(counts_aquila)
Gráfico de barras mostrando a taxa de disparos com e sem estados vizinhos “ativos”.

Observe que uma pequena fração das fotos tem locais vazios (marcados com “e”). Isso se deve às imperfeições de preparação de 1— 2% por átomo do Aquila QPU. Além disso, os resultados coincidem com a simulação dentro da flutuação estatística esperada devido ao pequeno número de disparos.

Próximas etapas

Parabéns, agora você executou sua primeira AHS carga de trabalho no Amazon Braket usando AHS o simulador local e o Aquila QPU.

Para saber mais sobre a física de Rydberg, a Simulação Hamiltoniana Analógica e a Aquila dispositivo, consulte nossos exemplos de notebooks.