# --- # jupyter: # jupytext: # formats: ipynb,py:percent # hide_notebook_metadata: false # text_representation: # extension: .py # format_name: percent # format_version: '1.3' # jupytext_version: 1.14.5 # kernelspec: # display_name: .venv # language: python # name: python3 # --- # %% [markdown] # Download Script # Download Job # #
#

Job Files for Complete Examples

#

# To be able to run the complete examples without having to submit your program to hardware and wait, you'll # need to download the associated job files. These files contain the results of running the program on # the quantum hardware. # # You can download the job files by clicking the "Download Job" button above. You'll then need to place # the job file in the `data` directory that was created for you when you ran the `import` part of the script (alternatively you can make the directory yourself, it should live at the same level as wherever you put this script). #

#
# # %% [markdown] # # Single Qubit Rabi Oscillations # ## Introduction # In this example we show how to use Bloqade to emulate Rabi oscillations of a Neutral # Atom and run it on hardware. We will define a Rabi oscillation as a sequence with a # constant detuning and Rabi frequency. In practice, the Rabi frequency has to start # and end at 0.0, so we will use a piecewise linear function to ramp up and down the # Rabi frequency. # %% import os import numpy as np import matplotlib.pyplot as plt from bloqade.analog import cast, load, save, start if not os.path.isdir("data"): os.mkdir("data") # %% [markdown] # ## Define the program. # Below we define program with one atom, with constant detuning but variable Rabi # frequency, ramping up to "rabi_ampl" and then returning to 0.0. Note that the `cast` # function can be used to create a variable that can used in multiple places in the # program. These variables support basic arithmetic operations, such as addition, # subtraction, multiplication, and division. They also have `min` and `max` methods # that can be used in place of built-in python `min` and `max` functions, e.g. # `cast("a").min(cast("b"))`. # %% durations = cast(["ramp_time", "run_time", "ramp_time"]) rabi_oscillations_program = ( start.add_position((0, 0)) .rydberg.rabi.amplitude.uniform.piecewise_linear( durations=durations, values=[0, "rabi_ampl", "rabi_ampl", 0] ) .detuning.uniform.constant(duration=sum(durations), value="detuning_value") ) # %% [markdown] # ## Assign values to the variables in the program, # Once your program is built, you can use the `assign` method to assign values to the # variables in the program. These values must be numeric, and can be either `int`, # `float`, or `Decimal` (from the `decimal` module). Note that the `Decimal` type # is used to represent real numbers exactly, whereas `float` is a 64-bit floating # point number that is only accurate to about 15 decimal places. The `Decimal` # type is recommended for Bloqade programs, as it will ensure that your program # is simulated and run with the highest possible precision. We can also do a parameter # scan using the `batch_assign` method, which will create a different program for each # value provided in the list. In this case, we are sweeping the `run_time` variable, # which is the time that the Rabi amplitude stays at the value of `rabi_ampl`. # %% run_times = np.linspace(0, 3, 101) rabi_oscillation_job = rabi_oscillations_program.assign( ramp_time=0.06, rabi_ampl=15, detuning_value=0.0 ).batch_assign(run_time=run_times) # %% [markdown] # ## Run Emulator and Hardware # To run the program on the emulator we can select the `bloqade` provider as a property # of the `batch` object. Bloqade has its own emulator that we can use to run the program. # To do this select `python()` as the next option followed by the `run` method. # Then we dump the results to a file so that we can use them later. Note that unlike the # actual hardware the shots do not correspond to multiple executions of the emulator, # but rather the number of times the final wavefunction is sampled. This is because the # emulator does not simulate any noise. # %% emu_filename = os.path.join(os.path.abspath(""), "data", "rabi-emulation.json") if not os.path.isfile(emu_filename): emu_batch = rabi_oscillation_job.bloqade.python().run(10000) save(emu_batch, emu_filename) # %% [markdown] # When running on the hardware we can use the `braket` provider. However, we will need # to specify the device to run on. In this case we will use *Aquila* via the `aquila` # method. Before that we must note that because Aquila can support up to 256 atoms in # an area that is $75 \times 76 \mu m^2$. We need to make full use of the capabilities # of the device. Bloqade automatically takes care of this with the `parallelize` method, # which will allow us to run multiple copies of the program in parallel using the full # user provided area of Aquila. The `parallelize` method takes a single argument, which # is the distance between each copy of the program on a grid. In this case, we want to # make sure that the distance between each atom is at least 24 micrometers, so that the # Rydberg interactions between atoms are negligible. # # To run the program but not wait for the results, we can use the `run_async` method, # which will return a `Batch` object that can be used to fetch the results later. After # running the program, we dump the results to a file so that we can use them later. Note # that if you want to wait for the results in the python script just call the `run` # method instead of `run_async`. This will block the script until all results in the # batch are complete. #
#

Hardware Execution Cost

#

# # For this particular program, 101 tasks are generated with each task having 1000 shots, amounting to # __USD \\$1040.30__ on AWS Braket. # #

#
# %% hardware_filename = os.path.join(os.path.abspath(""), "data", "rabi-job.json") if not os.path.isfile(hardware_filename): batch = rabi_oscillation_job.parallelize(24).braket.aquila().run_async(1000) save(batch, hardware_filename) # %% [markdown] # ## Plotting the Results # The quantity of interest in this example is the probability of finding the atom # in the Rydberg state, which is given by the `0` measurement outcome. The reason # that `0` is the Rydberg state is because the in the actual device the Rydberg # atoms are pushed out of the trap area and show up as a dark spot in the image. # To get the probability of being in the Rydberg state, we can use the `bitstrings` # method of the `Report` object, which returns a list of numpy arrays containing # the measurement outcomes for each shot. We can then use the `mean` method of # the numpy array to get the probability of being in the Rydberg state for each # shot. We can then plot the results as a function of time. the time value can be # obtained from the `run_time` parameter of the `Report` object as a list. # # before that we need to load the results from our previously saved files using # the `load` function from bloqade.analog: # %% emu_batch = load(emu_filename) hardware_batch = load(hardware_filename) # hardware_batch.fetch() # save(filename, hardware_batch) # %% hardware_report = hardware_batch.report() emulator_report = emu_batch.report() times = emulator_report.list_param("run_time") density = [1 - ele.mean() for ele in emulator_report.bitstrings()] plt.plot(times, density, color="#878787", marker=".", label="Emulator") times = hardware_report.list_param("run_time") density = [1 - ele.mean() for ele in hardware_report.bitstrings()] plt.plot(times, density, color="#6437FF", linewidth=4, label="QPU") plt.xlabel("Time ($\mu s$)") plt.ylabel("Rydberg population") plt.legend() plt.show()