State preparation with the SLM mask

Basics

When performing quantum computations with global pulses, it might be hard to prepare the system in an arbitrary initial state. This is especially true in the XY mode, where only a global \(\sigma^x\) pulse can produce excitations whose number is otherwise conserved during free evolution. A partial solution to this problem is to utilize an SLM mask. Assume a system of three qubits in XY mode is initially in state \(\left| 000 \right\rangle\), and that we are interested in preparing the state \(\left| 100 \right\rangle\). Acting naively with a global \(\sigma^x\) pulse of area \(\pi\) would result in state \(\left| 111\right\rangle\). Using an SLM pattern, however, it is possible to detune the last two qubits away from resonance, and the same global \(\sigma^x\) pulse will produced instead the desired state \(\left| 100\right\rangle\). Let’s see how it works in practice. First create the register:

[1]:
import numpy as np
from pulser import Pulse, Sequence, Register
from pulser.devices import MockDevice
from pulser.waveforms import BlackmanWaveform
from pulser_simulation import QutipEmulator

# Qubit register
qubits = {"q0": (-5, 0), "q1": (0, 0), "q2": (5, 0)}
reg = Register(qubits)
reg.draw()
../_images/tutorials_slm_mask_3_0.png

SLM Mask in XY mode

Let’s now create the sequence and add a global \(\sigma^x\) pulse of area \(\pi\) in XY mode:

[2]:
# Create the sequence
seq = Sequence(reg, MockDevice)

# Declare a global XY channel and add the pi pulse
seq.declare_channel("ch", "mw_global")
pulse = Pulse.ConstantDetuning(BlackmanWaveform(200, np.pi), 0, 0)
seq.add(pulse, "ch")

Drawing the sequence will show the following:

[3]:
seq.draw()
../_images/tutorials_slm_mask_8_0.png

To set up the SLM mask all we need to do is to pass to the \(\verb:Sequence.config_slm_mask:\) method a list that contains the name of the qubits that we want to mask and the name of the DMM channel to use to configure the SLM Mask. The latter is taken as dmm_0 by default. The device MockDevice contains one DMM, so dmm_0 does indeed exist.

[4]:
# Mask the last two qubits
masked_qubits = ["q1", "q2"]
seq.config_slm_mask(masked_qubits)

At this point it is possible to visualize the mask by drawing the sequence. The masked pulse will appear with a shaded background, and the names of the masked qubits will be shown in the bottom left corner.

[5]:
seq.draw()
../_images/tutorials_slm_mask_12_0.png

The sequence drawing method also allows to visualize the register. If an SLM mask is defined, the masked qubits will appear with a shaded square halo around them:

[6]:
seq.draw(draw_register=True)
../_images/tutorials_slm_mask_14_0.png
../_images/tutorials_slm_mask_14_1.png

Now let’s see how the system evolves under this masked pulse. Since the pulse only acts on the first qubit, we expect the final state to be \(\left| 100 \right\rangle\), or, according to Pulser’s conventions for XY basis states, \((0,1)^T \otimes (1,0)^T \otimes (1,0)^T\) in the Hilbert space \(C^8\):

[7]:
import qutip

qutip.tensor(qutip.basis(2, 1), qutip.basis(2, 0), qutip.basis(2, 0))
[7]:
Quantum object: dims = [[2, 2, 2], [1, 1, 1]], shape = (8, 1), type = ket $ \\ \left(\begin{matrix}0.0\\0.0\\0.0\\0.0\\1.0\\0.0\\0.0\\0.0\\\end{matrix}\right)$

Now run the simulation and print the final state as given by Pulser:

[8]:
sim = QutipEmulator.from_sequence(seq)
results = sim.run()

results.get_final_state()
[8]:
Quantum object: dims = [[2, 2, 2], [1, 1, 1]], shape = (8, 1), type = ket $ \\ \left(\begin{matrix}1.050\times10^{-05}j\\0.0\\0.0\\0.0\\1.000\\0.0\\0.0\\0.0\\\end{matrix}\right)$

As expected, the two states agree up to numerical errors.

Notes on XY mode

Since the SLM mask is mostly useful for state preparation, its use in Pulser is restricted to the first pulse in the sequence. This can be seen by adding an extra pulse in the previous example and drawing the sequence:

[9]:
seq.add(pulse, "ch")
[10]:
seq.draw()
../_images/tutorials_slm_mask_23_0.png

This example also illustrates the fact that in XY mode, the SLM mask can be configured at any moment during the creation of a sequence (either before or after adding pulses) and it will automatically latch to the first pulse. However, in order to reflect real hardware constraints, the mask can be configured only once. Trying to configure the mask a second time will raise an error:

[11]:
try:
    seq.config_slm_mask(masked_qubits)
except ValueError as err:
    print(err)
SLM mask can be configured only once.

SLM Mask in Ising mode

In Ising mode, configuring an SLM Mask with a DMM labeled dmm_id in the device internally configures a detuning map using config_detuning_map (see notebook “Local Addressability with DMM” for an introduction) with dmm_id and a DetuningMap distributing all the applied detuning to the masked qubits.

For instance in the last example qubits “q1” and “q2” are masked, so we expect a DetuningMap associating to the trap location of “q0” the weight \(0\), and to the trap locations of “q1” and “q2” the weight \(1\):

[12]:
# Create the sequence
seq = Sequence(reg, MockDevice)

# Declare a global Rydberg channel and add the pi pulse
seq.declare_channel("ch", "rydberg_global")
pulse = Pulse.ConstantDetuning(BlackmanWaveform(200, np.pi), 0, 0)
seq.add(pulse, "ch")
# Mask the last two qubits
masked_qubits = ["q1", "q2"]
seq.config_slm_mask(masked_qubits, "dmm_0")
seq._schedule["dmm_0"].detuning_map.draw([0, 1, 2])
../_images/tutorials_slm_mask_28_0.png

We can see that the shade of the square covering qubit 1 and 2 are the same, and that no square covers qubit 0: the detuning map created is exactly the one wanted.

Now what happens at the pulse level ?

If the SLM Mask is configured while some pulses had already been added to the sequence- as it is the case in the previous example- a pulse of constant detuning is immediately applied on the detuning map configured for the SLM Mask. The value of this detuning is very large compared to the amplitude of the first pulse in the schedule (ideally, -10 times this amplitude, or bottom_detuning of the DMM if bottom_detunig is defined and its value is higher).

[13]:
seq.draw(draw_detuning_maps=True, draw_qubit_amp=True, draw_qubit_det=True)
../_images/tutorials_slm_mask_30_0.png
../_images/tutorials_slm_mask_30_1.png
../_images/tutorials_slm_mask_30_2.png
../_images/tutorials_slm_mask_30_3.png

At the qubit level, you can see that all the qubits receive the \(\sigma^x\) pulse of area \(\pi\) in amplitude, but only qubit q0 is on resonance (detuning is constant equal to 0) and will change state. Detuning on “q1” and “q2” being not of infinite values, they are not perfectly masked and therefore the state resulting from this sequence is not exactly the state \((0,1)^T \otimes (1,0)^T \otimes (1,0)^T\). However it is very close to this state and represents better the experiment.

[14]:
sim = QutipEmulator.from_sequence(seq)
results = sim.run()

results.get_final_state()
[14]:
Quantum object: dims = [[2, 2, 2], [1, 1, 1]], shape = (8, 1), type = ket $ \\ \left(\begin{matrix}(-3.869\times10^{-11}+1.209\times10^{-10}j)\\(7.417\times10^{-06}-4.778\times10^{-06}j)\\(-2.179\times10^{-07}+3.557\times10^{-06}j)\\1.000\\(-4.207\times10^{-10}+1.881\times10^{-11}j)\\(1.860\times10^{-05}-5.777\times10^{-05}j)\\(9.521\times10^{-06}+1.005\times10^{-06}j)\\(-0.011+2.148\times10^{-04}j)\\\end{matrix}\right)$

Note: Of course, you can add pulses to channels or dmm after the SLM Mask:

[15]:
seq.add(pulse, "ch")
seq.add_dmm_detuning(-BlackmanWaveform(200, np.pi), "dmm_0")
seq.draw()
../_images/tutorials_slm_mask_34_0.png

If the first operation performed in the sequence is the configuration of the SLM Mask, then the DMM used for the configuration of the SLM Mask cannot be used until a pulse is added on an Ising channel. The first pulse added will define the SLM Mask.

[16]:
# Create the sequence
seq = Sequence(reg, MockDevice)
# Mask the last two qubits
masked_qubits = ["q1", "q2"]
seq.config_slm_mask(masked_qubits, "dmm_0")
# Declare a global Rydberg channel and add the pi pulse
seq.declare_channel("ch", "rydberg_global")

try:
    seq.add_dmm_detuning(-BlackmanWaveform(200, np.pi), "dmm_0")
except ValueError as err:
    print(err)
You should add a Pulse to a Global Channel prior to modulating the DMM used for the SLM Mask.
[17]:
# Masked pulse
seq.add(pulse, "ch")
seq.draw()
../_images/tutorials_slm_mask_37_0.png

Note:

As in XY mode, you cannot configure the SLM Mask twice. However, be careful that in Ising mode, the moment you configure the SLM Mask matters: once configured, the masked pulse is never changed.