Skip to content
Pasqal Documentation

Getting started

This tutorial shows how to use emu-sv as a Pulser backend to run a pulse sequence and extract observables. We emulate the preparation of an antiferromagnetic (AFM) state on a 1D ring of atoms using state vector simulation.

Emu-sv is a state-vector emulator designed for medium-sized systems (~25 qubits), providing exact simulation of neutral-atom quantum hardware. For larger systems, see the emu-mps tutorial which uses Matrix Product States.

To illustrate the simulation of sequences, let us study a simple one-dimensional system with periodic boundary conditions (a ring of atoms):

import numpy as np
import matplotlib.pyplot as plt
import pulser
# Setup
L = 10
Omega_max = 2.3 * 2 * np.pi
U = Omega_max / 2.3
delta_0 = -3 * U
delta_f = 1 * U
t_rise = 2000
t_fall = 2000
t_sweep = (delta_f - delta_0) / (2 * np.pi * 10) * 5000
# Define a ring of atoms distanced by a blockade radius distance:
R_interatomic = pulser.MockDevice.rydberg_blockade_radius(U)
coords = (
R_interatomic
/ (2 * np.sin(np.pi / L))
* np.array(
[
(np.cos(theta * 2 * np.pi / L), np.sin(theta * 2 * np.pi / L))
for theta in range(L)
]
)
)
# ring, periodic register
reg = pulser.Register.from_coordinates(coords, prefix="q")
# or try open boundaries
#reg = pulser.Register.rectangle(1,L,spacing=R_interatomic/1.2, prefix="q")
reg.draw(blockade_radius=R_interatomic, draw_half_radius=True, draw_graph=True)
rise = pulser.Pulse.ConstantDetuning(
pulser.RampWaveform(t_rise, 0.0, Omega_max), delta_0, 0.0
)
sweep = pulser.Pulse.ConstantAmplitude(
Omega_max, pulser.RampWaveform(t_sweep, delta_0, delta_f), 0.0
)
fall = pulser.Pulse.ConstantDetuning(
pulser.RampWaveform(t_fall, Omega_max, 0.0), delta_f, 0.0
)
seq = pulser.Sequence(reg, pulser.MockDevice)
seq.declare_channel("global", "rydberg_global")
seq.add(rise, "global")
seq.add(sweep, "global")
seq.add(fall, "global")
seq.draw()
from emu_sv import StateResult, SVConfig, Occupation
dt = 10 # timestep in ns
seq_duration = seq.get_duration()
eval_times = [t/seq_duration for t in range(dt, seq_duration, dt)]
final_time = eval_times[-1]
density = Occupation(evaluation_times=eval_times)
state = StateResult(evaluation_times=[final_time]) # get the state at final time
config = SVConfig(dt=dt, observables = [density, state], log_level=2000)
from emu_sv import SVBackend
bknd = SVBackend(seq, config=config)
results = bknd.run()

The Results object returned at the end of an emulation run, is a dictionary that contains the observables defined above, at each time step. We can access them using their name and time. The Results object that we created contains the final quantum state for example:

last_elem = -1
results.state[last_elem].vector[:10]
last_elem = -1
final_state = results.state[last_elem]
counts = final_state.sample(num_shots=1000)
large_counts = {k: v for k, v in counts.items() if v > 10}
plt.figure(figsize=(15, 4))
plt.xticks(rotation=90, fontsize=14)
plt.title("Most frequent observations")
plt.bar(large_counts.keys(), large_counts.values())
Terminal window
times = np.array(eval_times)*seq.get_duration()
#
array_of_occup = [np.array(results.occupation[i]) for i, t in enumerate(times)]
occup = np.stack(array_of_occup, axis=1)
fig, axs = plt.subplots(1,2, figsize=(8,4))
for i in range(L):
axs[0].plot(times[0:], occup[i,:])
axs[0].set_xlabel("time [ns]")
axs[0].set_ylabel(r"$\langle n_i\rangle$")
pc = axs[1].pcolor(times, range(L), occup)
axs[1].set_xlabel("time [ns]")
axs[1].set_ylabel("qubit")
fig.colorbar(pc, ax=axs[1], label = r"$\langle Z_i\rangle$")