# Memory spectroscopy Using {mod}`.pulsed` mode, we measure the frequency of the bosonic mode. We sweep the frequency of the displacement pulse sent to the memory. Following the displacement pulse, we send a long selective $\pi$ pulse to the qubit and finally the readout pulse to the readout resonator. The memory displacement pulse has a $\sin^2$ envelope. The qubit-control pulse is a selective $\pi$ pulse with a $\sin^2$ envelope whose frequency is the frequency of the qubit when the cavity is in the Fock state $|0\rangle$. The readout pulse is at the frequency of the readout resonator and has a square envelope. By probing the state of the qubit, we directly get the probablility that the cavity is in state $|0\rangle$. ![Memory spectroscopy sequence](images/sweep_memory_pulse_sequence.svg){align=center} The class for performing the memory spectroscopy experiment is available at [presto-measure/sweep_memory.py][sweep_memory.py]. Here, we run the experiment and observe the qubit population reducing when the frequency of the memory displacement drive equals the memory frequency. This is because the memory displacemet drive displaced the memory state into some coherent state and the probability to find the memory in state $|0\rangle$ reduced. We then have a look at the main parts of the code. You can create a new experiment and run it on your Presto. Be sure to change the parameters of `Sweep_memory` to match your experiment and change `presto_address` to match the IP address of your Presto: ```python from sweep_memory import Sweep_memory import numpy as np experiment = Sweep_memory( readout_freq=6.2e9, control_freq=4.2e9, memory_freq_center=3.5e9, memory_freq_span=100e6, memory_freq_nr=101, readout_amp=0.1, control_amp=0.5, memory_amp=0.2, readout_duration=2.5e-6, control_duration=2000e-9, memory_duration=50e-9, sample_duration=2.5e-6, readout_port=1, control_port=2, memory_port=5, sample_port=1, wait_delay=50e-6, readout_sample_delay=0e-9, num_averages=100, ) presto_address = "192.168.88.65" # your Presto IP address save_filename = experiment.run(presto_address) ``` Or you can also load older data: ```python experiment = Sweep_memory.load("../data/sweep_memory_20240129_155938.h5") ``` In either case, we analyze the data to get a nice plot: ```python experiment.analyze() ``` ```{image} images/sweep_memory_light.svg :align: center :class: only-light ``` ```{image} images/sweep_memory_dark.svg :align: center :class: only-dark ``` The qubit is in the excited state for all the frequencies of the memory drive that are different than the memory frequency. This is because the memory is in the state $|0\rangle$ and the selective qubit $\pi$ pulse can excite the qubit. When the memory drive frequency is equal to the memory frequency, the memory state becomes a coherent state $|\alpha\rangle$ and the probablility of memory being in the ground state $|0\rangle$ reduces. Hence, the selective qubit $\pi$ pulse will only partially excite the qubit and we will observe a reduction in the qubit population. Data is fitted to extract the frequency of the memory. The complex-valued readout signal is rotated using {func}`.utils.rotate_opt` so that all the information about the qubit state is in the I quadrature. ## Code explanation Here we discuss the main part of the code of the `Sweep_memory` class: the definition of the experiment sequence. The full source is available at [presto-measure/sweep_memory.py][sweep_memory.py]. Since we want to sweep the frequency of the memory pulse we should setup the frequency lookup table. We first create an array of all the memory frequencies ```python self.memory_freq_arr = self.memory_freq_center + np.linspace( -self.memory_freq_span / 2, self.memory_freq_span / 2, self.memory_freq_nr ) ``` Now we should convert this array into an array of intermediate frequencies IF and phases that are sent to the I and Q ports of the digital mixer. We set `self.memory_freq_center` as the LO frequency, so IF frequencies are the difference between the `self.memory_freq_arr` and the LO frequency. ```python memory_if_arr = self.memory_freq_arr - self.memory_freq_center ``` If IF frequency is positive this means we should output the high sideband, meaning phase Q should be $\pi/2$ smaller than phase I: `ph_q = ph_i - np.pi / 2`. However, if IF frequency is negative we should output the lower sideband, meaning phase Q should be $\pi/2$ bigger than phase I: `ph_q = ph_i + np.pi / 2`. Frequency has to be a positive number so we will have to take the absolute value of `memory_if_arr` and assign appropriate phases to each IF frequency. All of this is compactly written in the following lines of code: ```python mask = np.ma.masked_less(memory_if_arr, 0).mask memory_if_arr = np.abs(memory_if_arr) ph_i = np.zeros_like(memory_if_arr) ph_q = ph_i - np.pi / 2 + mask * np.pi # +-np.pi/2 for low/high sideband ``` `mask` is an array where an element is equal to 1 if the same element of `memory_if_arr` is smaller than zero. All the other elements of `mask` are equal to 0. We should setup the memory template and indicate that this template will be multiplied with the IF generator by setting the `envelope=True`: ```python memory_ns = int(round(self.memory_duration * pls.get_fs("dac"))) memory_envelope = sin2(memory_ns) memory_pulse = pls.setup_template( self.memory_port, group=0, template=memory_envelope + 1j * memory_envelope, envelope=True, ) ``` :::{note} All other pulses and scale lookup tables are set in a similar way like in the qubit tutorial. See for example [Rabi amplitude](../tutorial_qubits/rabi_amp) for more details. ::: We program the pulse sequence: ```python T = 0.0 # s, start at time zero ... pls.reset_phase(T, self.memory_port, group=0) pls.output_pulse(T, memory_pulse) # displace memory T += self.memory_duration pls.output_pulse(T, control_pulse) # pi pulse conditioned on memory in |0> T += self.control_duration pls.output_pulse(T, readout_pulse) # Readout pls.store(T + self.readout_sample_delay) T += self.readout_duration pls.next_frequency(T, self.memory_port, group=0) T += self.wait_delay # Wait for decay ``` We first output the memory displacement pulse, then we output qubit-control $\pi$ pulse that maps the population of Fock state $|0\rangle$ in the memory onto qubit population. Finally, we perform the readout. We call {meth}`.Pulsed.next_frequency` to sweep the frequency of the memory pulse. --- {meth}`~.Pulsed.run` executes the experiment sequence. We set `repeat_count=self.memory_freq_nr` since we want to sweep through `self.memory_freq_nr` frequencies. ```python pls.run(period=T, repeat_count=self.memory_freq_nr, num_averages=self.num_averages) ``` [sweep_memory.py]: https://github.com/intermod-pro/presto-measure/blob/master/sweep_memory.py