diff --git a/0.13.0/examples/example-1-floquet/index.html b/0.13.0/examples/example-1-floquet/index.html index b924b26..d7effa8 100644 --- a/0.13.0/examples/example-1-floquet/index.html +++ b/0.13.0/examples/example-1-floquet/index.html @@ -386,14 +386,14 @@ display: inline-block; white-space: normal; } -
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).
Rounding out the single qubit examples we will show how to generate a Floquet protocol. We will define the probotol using a python function and then use the Bloqade API to sample the function at certain intervals to make it compatible with the hardware, which only supports piecewise linear/constant functions. First let us start with the imports.
from bloqade import start, cast, save, load
+ 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).
Single Qubit Floquet Dynamics¶
Introduction¶
Rounding out the single qubit examples we will show how to generate a Floquet protocol. We will define the probotol using a python function and then use the Bloqade API to sample the function at certain intervals to make it compatible with the hardware, which only supports piecewise linear/constant functions. First let us start with the imports.
In [1]: Copied! from bloqade import start, cast, save, load
import os
import numpy as np
import matplotlib.pyplot as plt
if not os.path.isdir("data"):
os.mkdir("data")
-
from bloqade import start, cast, save, load import os import numpy as np import matplotlib.pyplot as plt if not os.path.isdir("data"): os.mkdir("data") Define the program.¶
For the floquet protocol we keep We do the same Rabi drive but allow the detuning to vary sinusoidally. We do this by defining a smooth function for the detuning and then sampling it at certain intervals (in this case, the minimum hardware-supported time step). Note that the sample
method will always sample at equal to or greater than the specified time step. If the total time interval is not divisible by the time step, the last time step will be larger than the specified time step. Also note that the arguments of your function must be named arguments, e.g. no *args
or **kwargs
, because Bloqade will analyze the function signature to and generate variables for each argument.
In [2]: Copied! min_time_step = 0.05
+
from bloqade import start, cast, save, load import os import numpy as np import matplotlib.pyplot as plt if not os.path.isdir("data"): os.mkdir("data") Define the program.¶
For the floquet protocol we keep We do the same Rabi drive but allow the detuning to vary sinusoidally. We do this by defining a smooth function for the detuning and then sampling it at certain intervals (in this case, the minimum hardware-supported time step). Note that the sample
method will always sample at equal to or greater than the specified time step. If the total time interval is not divisible by the time step, the last time step will be larger than the specified time step. Also note that the arguments of your function must be named arguments, e.g. no *args
or **kwargs
, because Bloqade will analyze the function signature to and generate variables for each argument.
In [2]: Copied! min_time_step = 0.05
durations = cast(["ramp_time", "run_time", "ramp_time"])
@@ -410,7 +410,7 @@
.detuning.uniform.fn(detuning_wf, sum(durations))
.sample("min_time_step", "linear")
)
-
min_time_step = 0.05 durations = cast(["ramp_time", "run_time", "ramp_time"]) def detuning_wf(t, drive_amplitude, drive_frequency): return drive_amplitude * np.sin(drive_frequency * t) floquet_program = ( start.add_position((0, 0)) .rydberg.rabi.amplitude.uniform.piecewise_linear( durations, [0, "rabi_max", "rabi_max", 0] ) .detuning.uniform.fn(detuning_wf, sum(durations)) .sample("min_time_step", "linear") ) We assign values to the necessary variables and then run_async the program to both the emulator and actual hardware.
In [3]: Copied! run_times = np.linspace(0.05, 3.0, 101)
+
min_time_step = 0.05 durations = cast(["ramp_time", "run_time", "ramp_time"]) def detuning_wf(t, drive_amplitude, drive_frequency): return drive_amplitude * np.sin(drive_frequency * t) floquet_program = ( start.add_position((0, 0)) .rydberg.rabi.amplitude.uniform.piecewise_linear( durations, [0, "rabi_max", "rabi_max", 0] ) .detuning.uniform.fn(detuning_wf, sum(durations)) .sample("min_time_step", "linear") ) We assign values to the necessary variables and then run_async the program to both the emulator and actual hardware.
In [3]: Copied! run_times = np.linspace(0.05, 3.0, 101)
floquet_job = floquet_program.assign(
ramp_time=0.06,
@@ -419,7 +419,7 @@
drive_amplitude=15,
drive_frequency=15,
).batch_assign(run_time=run_times)
-
run_times = np.linspace(0.05, 3.0, 101) floquet_job = floquet_program.assign( ramp_time=0.06, min_time_step=0.05, rabi_max=15, drive_amplitude=15, drive_frequency=15, ).batch_assign(run_time=run_times) have to start the time at 0.05 because the hardware does not support anything less than that time step. We can now run_async the job to the emulator and hardware.
Run Emulator and Hardware¶
Like in the first tutorial, we will run the program on the emulator and hardware. Note that for the hardware we will use the parallelize
method to run multiple copies of the program in parallel. For more information about this process, see the first tutorial.
In [4]: Copied! emu_filename = os.path.join(os.path.abspath(""), "data", "floquet-emulation.json")
+
run_times = np.linspace(0.05, 3.0, 101) floquet_job = floquet_program.assign( ramp_time=0.06, min_time_step=0.05, rabi_max=15, drive_amplitude=15, drive_frequency=15, ).batch_assign(run_time=run_times) have to start the time at 0.05 because the hardware does not support anything less than that time step. We can now run_async the job to the emulator and hardware.
Run Emulator and Hardware¶
Like in the first tutorial, we will run the program on the emulator and hardware. Note that for the hardware we will use the parallelize
method to run multiple copies of the program in parallel. For more information about this process, see the first tutorial.
In [4]: Copied! emu_filename = os.path.join(os.path.abspath(""), "data", "floquet-emulation.json")
if not os.path.isfile(emu_filename):
emu_batch = floquet_job.braket.local_emulator().run(10000)
@@ -430,11 +430,11 @@
if not os.path.isfile(hardware_filename):
batch = floquet_job.parallelize(24).braket.aquila().run_async(shots=50)
save(batch, hardware_filename)
-
emu_filename = os.path.join(os.path.abspath(""), "data", "floquet-emulation.json") if not os.path.isfile(emu_filename): emu_batch = floquet_job.braket.local_emulator().run(10000) save(emu_batch, emu_filename) hardware_filename = os.path.join(os.path.abspath(""), "data", "floquet-job.json") if not os.path.isfile(hardware_filename): batch = floquet_job.parallelize(24).braket.aquila().run_async(shots=50) save(batch, hardware_filename) Plotting the Results¶
Exactly like in the Rabi Oscillation example, we can now plot the results from the hardware and emulation together. Again we will use the report
to calculate the mean Rydberg population for each run, and then plot the results.
first we load the results from the emulation and hardware.
In [5]: Copied! emu_batch = load(emu_filename)
+
emu_filename = os.path.join(os.path.abspath(""), "data", "floquet-emulation.json") if not os.path.isfile(emu_filename): emu_batch = floquet_job.braket.local_emulator().run(10000) save(emu_batch, emu_filename) hardware_filename = os.path.join(os.path.abspath(""), "data", "floquet-job.json") if not os.path.isfile(hardware_filename): batch = floquet_job.parallelize(24).braket.aquila().run_async(shots=50) save(batch, hardware_filename) Plotting the Results¶
Exactly like in the Rabi Oscillation example, we can now plot the results from the hardware and emulation together. Again we will use the report
to calculate the mean Rydberg population for each run, and then plot the results.
first we load the results from the emulation and hardware.
In [5]: Copied! emu_batch = load(emu_filename)
hardware_batch = load(hardware_filename)
# hardware_batch.fetch()
# save(filename, hardware_batch)
-
emu_batch = load(emu_filename) hardware_batch = load(hardware_filename) # hardware_batch.fetch() # save(filename, hardware_batch) Next we extract the run times and the Rydberg population from the report. We can then plot the results.
In [6]: Copied! hardware_report = hardware_batch.report()
+
emu_batch = load(emu_filename) hardware_batch = load(hardware_filename) # hardware_batch.fetch() # save(filename, hardware_batch) Next we extract the run times and the Rydberg population from the report. We can then plot the results.
In [6]: Copied! hardware_report = hardware_batch.report()
emulator_report = emu_batch.report()
times = emulator_report.list_param("run_time")
diff --git a/0.13.0/examples/example-1-rabi/index.html b/0.13.0/examples/example-1-rabi/index.html
index 0103c57..906df68 100644
--- a/0.13.0/examples/example-1-rabi/index.html
+++ b/0.13.0/examples/example-1-rabi/index.html
@@ -386,14 +386,14 @@
display: inline-block;
white-space: normal;
}
- 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).
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.
In [1]: Copied! from bloqade import start, cast, load, save
+ 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).
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.
In [1]: Copied! from bloqade import start, cast, load, save
import os
import matplotlib.pyplot as plt
import numpy as np
if not os.path.isdir("data"):
os.mkdir("data")
-
from bloqade import start, cast, load, save import os import matplotlib.pyplot as plt import numpy as np if not os.path.isdir("data"): os.mkdir("data") 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"))
.
In [2]: Copied! durations = cast(["ramp_time", "run_time", "ramp_time"])
+
from bloqade import start, cast, load, save import os import matplotlib.pyplot as plt import numpy as np if not os.path.isdir("data"): os.mkdir("data") 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"))
.
In [2]: Copied! durations = cast(["ramp_time", "run_time", "ramp_time"])
rabi_oscillations_program = (
start.add_position((0, 0))
@@ -402,26 +402,26 @@
)
.detuning.uniform.constant(duration=sum(durations), value="detuning_value")
)
-
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") ) 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 numberthat 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
.
In [3]: Copied! run_times = np.linspace(0, 3, 101)
+
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") ) 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 numberthat 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
.
In [3]: Copied! 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)
-
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) Run Emulator and Hardware¶
To run the program on the emulator we can select the braket
provider as a property of the batch
object. Braket has its own emulator that we can use to run the program. To do this select local_emulator
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 unline the actual hardware the shots do not correspond to multiple executions of the emuatlor, but rather the number of times the final wavefunction is sampled. This is because the emulator does not simulate any noise.
In [4]: Copied! emu_filename = os.path.join(os.path.abspath(""), "data", "rabi-emulation.json")
+
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) Run Emulator and Hardware¶
To run the program on the emulator we can select the braket
provider as a property of the batch
object. Braket has its own emulator that we can use to run the program. To do this select local_emulator
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 unline the actual hardware the shots do not correspond to multiple executions of the emuatlor, but rather the number of times the final wavefunction is sampled. This is because the emulator does not simulate any noise.
In [4]: Copied! emu_filename = os.path.join(os.path.abspath(""), "data", "rabi-emulation.json")
if not os.path.isfile(emu_filename):
emu_batch = rabi_oscillation_job.braket.local_emulator().run(10000)
save(emu_batch, emu_filename)
-
emu_filename = os.path.join(os.path.abspath(""), "data", "rabi-emulation.json") if not os.path.isfile(emu_filename): emu_batch = rabi_oscillation_job.braket.local_emulator().run(10000) save(emu_batch, emu_filename) 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.
In [5]: Copied! hardware_filename = os.path.join(os.path.abspath(""), "data", "rabi-job.json")
+
emu_filename = os.path.join(os.path.abspath(""), "data", "rabi-emulation.json") if not os.path.isfile(emu_filename): emu_batch = rabi_oscillation_job.braket.local_emulator().run(10000) save(emu_batch, emu_filename) 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.
In [5]: Copied! 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)
-
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) 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:
In [6]: Copied! emu_batch = load(emu_filename)
+
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) 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:
In [6]: Copied! emu_batch = load(emu_filename)
hardware_batch = load(hardware_filename)
# hardware_batch.fetch()
# save(filename, hardware_batch)
-
emu_batch = load(emu_filename) hardware_batch = load(hardware_filename) # hardware_batch.fetch() # save(filename, hardware_batch) In [7]: Copied! hardware_report = hardware_batch.report()
+
emu_batch = load(emu_filename) hardware_batch = load(hardware_filename) # hardware_batch.fetch() # save(filename, hardware_batch) In [7]: Copied! hardware_report = hardware_batch.report()
emulator_report = emu_batch.report()
times = emulator_report.list_param("run_time")
diff --git a/0.13.0/examples/example-1-ramsey/index.html b/0.13.0/examples/example-1-ramsey/index.html
index 8f5e085..317d2b0 100644
--- a/0.13.0/examples/example-1-ramsey/index.html
+++ b/0.13.0/examples/example-1-ramsey/index.html
@@ -386,7 +386,7 @@
display: inline-block;
white-space: normal;
}
- 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).
Single Qubit Ramsey Protocol¶
Introduction¶
In this example we show how to use Bloqade to emulate a Ramsey protocol as well as run it on hardware. We will define a Ramsey protocol as a sequence of two $\pi/2$ pulses separated by a variable time gap $\tau$. These procols are used to measure the coherence time of a qubit. 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.
In [1]: Copied! from bloqade import start, cast, save, load
+ 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).
Single Qubit Ramsey Protocol¶
Introduction¶
In this example we show how to use Bloqade to emulate a Ramsey protocol as well as run it on hardware. We will define a Ramsey protocol as a sequence of two $\pi/2$ pulses separated by a variable time gap $\tau$. These procols are used to measure the coherence time of a qubit. 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.
In [1]: Copied! from bloqade import start, cast, save, load
from decimal import Decimal
import os
import numpy as np
@@ -394,7 +394,7 @@
if not os.path.isdir("data"):
os.mkdir("data")
-
from bloqade import start, cast, save, load from decimal import Decimal import os import numpy as np import matplotlib.pyplot as plt if not os.path.isdir("data"): os.mkdir("data") Define the program.¶
define program with one atom, with constant detuning but variable Rabi frequency, where an initial pi/2 pulse is applied, followed by some time gap and a -pi/2 pulse. Note that the plateau time was chosen such that the area under the curve is pi/2 given given the constraint on how fast the Rabi frequency can change as well as the minimum allowed time step.
In [2]: Copied! plateau_time = (np.pi / 2 - 0.625) / 12.5
+
from bloqade import start, cast, save, load from decimal import Decimal import os import numpy as np import matplotlib.pyplot as plt if not os.path.isdir("data"): os.mkdir("data") Define the program.¶
define program with one atom, with constant detuning but variable Rabi frequency, where an initial pi/2 pulse is applied, followed by some time gap and a -pi/2 pulse. Note that the plateau time was chosen such that the area under the curve is pi/2 given given the constraint on how fast the Rabi frequency can change as well as the minimum allowed time step.
In [2]: Copied! plateau_time = (np.pi / 2 - 0.625) / 12.5
wf_durations = cast([0.05, plateau_time, 0.05, "run_time", 0.05, plateau_time, 0.05])
rabi_wf_values = [0.0, 12.5, 12.5, 0.0] * 2 # repeat values twice
@@ -403,13 +403,13 @@
.rydberg.rabi.amplitude.uniform.piecewise_linear(wf_durations, rabi_wf_values)
.detuning.uniform.constant(10.5, sum(wf_durations))
)
-
plateau_time = (np.pi / 2 - 0.625) / 12.5 wf_durations = cast([0.05, plateau_time, 0.05, "run_time", 0.05, plateau_time, 0.05]) rabi_wf_values = [0.0, 12.5, 12.5, 0.0] * 2 # repeat values twice ramsey_program = ( start.add_position((0, 0)) .rydberg.rabi.amplitude.uniform.piecewise_linear(wf_durations, rabi_wf_values) .detuning.uniform.constant(10.5, sum(wf_durations)) ) Assign values to the variables in the program, allowing run_time
(time gap between the two pi/2 pulses) to sweep across a range of values.
In [3]: Copied! n_steps = 100
+
plateau_time = (np.pi / 2 - 0.625) / 12.5 wf_durations = cast([0.05, plateau_time, 0.05, "run_time", 0.05, plateau_time, 0.05]) rabi_wf_values = [0.0, 12.5, 12.5, 0.0] * 2 # repeat values twice ramsey_program = ( start.add_position((0, 0)) .rydberg.rabi.amplitude.uniform.piecewise_linear(wf_durations, rabi_wf_values) .detuning.uniform.constant(10.5, sum(wf_durations)) ) Assign values to the variables in the program, allowing run_time
(time gap between the two pi/2 pulses) to sweep across a range of values.
In [3]: Copied! n_steps = 100
max_time = Decimal("3.0")
dt = (max_time - Decimal("0.05")) / n_steps
run_times = [Decimal("0.05") + dt * i for i in range(101)]
ramsey_job = ramsey_program.batch_assign(run_time=run_times)
-
n_steps = 100 max_time = Decimal("3.0") dt = (max_time - Decimal("0.05")) / n_steps run_times = [Decimal("0.05") + dt * i for i in range(101)] ramsey_job = ramsey_program.batch_assign(run_time=run_times) Run Emulation and Hardware¶
Like in the first tutorial, we will run the program on the emulator and hardware. Note that for the hardware we will use the parallelize
method to run multiple copies of the program in parallel. For more information about this process, see the first tutorial.
In [4]: Copied! emu_filename = os.path.join(os.path.abspath(""), "data", "ramsey-emulation.json")
+
n_steps = 100 max_time = Decimal("3.0") dt = (max_time - Decimal("0.05")) / n_steps run_times = [Decimal("0.05") + dt * i for i in range(101)] ramsey_job = ramsey_program.batch_assign(run_time=run_times) Run Emulation and Hardware¶
Like in the first tutorial, we will run the program on the emulator and hardware. Note that for the hardware we will use the parallelize
method to run multiple copies of the program in parallel. For more information about this process, see the first tutorial.
In [4]: Copied! emu_filename = os.path.join(os.path.abspath(""), "data", "ramsey-emulation.json")
if not os.path.isfile(emu_filename):
emu_batch = ramsey_job.braket.local_emulator().run(10000)
@@ -419,11 +419,11 @@
if not os.path.isfile(hardware_filename):
batch = ramsey_job.parallelize(24).braket.aquila().run_async(shots=100)
save(batch, hardware_filename)
-
emu_filename = os.path.join(os.path.abspath(""), "data", "ramsey-emulation.json") if not os.path.isfile(emu_filename): emu_batch = ramsey_job.braket.local_emulator().run(10000) save(emu_batch, emu_filename) hardware_filename = os.path.join(os.path.abspath(""), "data", "ramsey-job.json") if not os.path.isfile(hardware_filename): batch = ramsey_job.parallelize(24).braket.aquila().run_async(shots=100) save(batch, hardware_filename) Plot the results¶
Exactly like in the Rabi Oscillation example, we can now plot the results from the hardware and emulation together. Again we will use the report
to calculate the mean Rydberg population for each run, and then plot the results.
first we load the results from the emulation and hardware.
In [5]: Copied! emu_batch = load(emu_filename)
+
emu_filename = os.path.join(os.path.abspath(""), "data", "ramsey-emulation.json") if not os.path.isfile(emu_filename): emu_batch = ramsey_job.braket.local_emulator().run(10000) save(emu_batch, emu_filename) hardware_filename = os.path.join(os.path.abspath(""), "data", "ramsey-job.json") if not os.path.isfile(hardware_filename): batch = ramsey_job.parallelize(24).braket.aquila().run_async(shots=100) save(batch, hardware_filename) Plot the results¶
Exactly like in the Rabi Oscillation example, we can now plot the results from the hardware and emulation together. Again we will use the report
to calculate the mean Rydberg population for each run, and then plot the results.
first we load the results from the emulation and hardware.
In [5]: Copied! emu_batch = load(emu_filename)
hardware_batch = load(hardware_filename)
# hardware_batch.fetch()
# save(filename, hardware_batch)
-
emu_batch = load(emu_filename) hardware_batch = load(hardware_filename) # hardware_batch.fetch() # save(filename, hardware_batch) Next we can calculate the Rydberg population for each run and plot the results.
In [6]: Copied! hardware_report = hardware_batch.report()
+
emu_batch = load(emu_filename) hardware_batch = load(hardware_filename) # hardware_batch.fetch() # save(filename, hardware_batch) Next we can calculate the Rydberg population for each run and plot the results.
In [6]: Copied! hardware_report = hardware_batch.report()
emulator_report = emu_batch.report()
times = emulator_report.list_param("run_time")
diff --git a/0.13.0/examples/example-2-multi-qubit-blockaded/index.html b/0.13.0/examples/example-2-multi-qubit-blockaded/index.html
index bc55f12..d9b742c 100644
--- a/0.13.0/examples/example-2-multi-qubit-blockaded/index.html
+++ b/0.13.0/examples/example-2-multi-qubit-blockaded/index.html
@@ -386,7 +386,7 @@
display: inline-block;
white-space: normal;
}
- 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).
Multi-qubit Blockaded Rabi Oscillations¶
Introduction¶
In this tutorial we will show you how to compose geometries with pulse sequences to perform multi-qubit blockaded Rabi oscillations. The Physics here is described in detail in the whitepaper. But in short, we can use the Rydberg blockade to change the effective Rabi frequency of the entire system by adding more atoms to the cluster.
In [1]: Copied! from bloqade import start, save, load
+ 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).
Multi-qubit Blockaded Rabi Oscillations¶
Introduction¶
In this tutorial we will show you how to compose geometries with pulse sequences to perform multi-qubit blockaded Rabi oscillations. The Physics here is described in detail in the whitepaper. But in short, we can use the Rydberg blockade to change the effective Rabi frequency of the entire system by adding more atoms to the cluster.
In [1]: Copied! from bloqade import start, save, load
from bloqade.atom_arrangement import Chain, Square
import numpy as np
import matplotlib.pyplot as plt
@@ -395,7 +395,7 @@
if not os.path.isdir("data"):
os.mkdir("data")
-
from bloqade import start, save, load from bloqade.atom_arrangement import Chain, Square import numpy as np import matplotlib.pyplot as plt import os if not os.path.isdir("data"): os.mkdir("data") Defining the Geometry¶
We will start by defining the geometry of the atoms. The idea here is to cluster the atoms so that they are all blockaded from each other. Using a combination of the Chain
and Square
classes, as a base, one can add additional atoms to the geometry using the add_position
method. This method takes a list of tuples, or a single tuple, of the form (x,y)
where x
and y
are the coordinates of the atom in units of the lattice constant.
In [2]: Copied! distance = 4.0
+
from bloqade import start, save, load from bloqade.atom_arrangement import Chain, Square import numpy as np import matplotlib.pyplot as plt import os if not os.path.isdir("data"): os.mkdir("data") Defining the Geometry¶
We will start by defining the geometry of the atoms. The idea here is to cluster the atoms so that they are all blockaded from each other. Using a combination of the Chain
and Square
classes, as a base, one can add additional atoms to the geometry using the add_position
method. This method takes a list of tuples, or a single tuple, of the form (x,y)
where x
and y
are the coordinates of the atom in units of the lattice constant.
In [2]: Copied! distance = 4.0
inv_sqrt_2_rounded = 2.6
geometries = {
@@ -417,17 +417,17 @@
]
),
}
-
distance = 4.0 inv_sqrt_2_rounded = 2.6 geometries = { 1: Chain(1), 2: Chain(2, lattice_spacing=distance), 3: start.add_position( [(-inv_sqrt_2_rounded, 0.0), (inv_sqrt_2_rounded, 0.0), (0, distance)] ), 4: Square(2, lattice_spacing=distance), 7: start.add_position( [ (0, 0), (distance, 0), (-0.5 * distance, distance), (0.5 * distance, distance), (1.5 * distance, distance), (0, 2 * distance), (distance, 2 * distance), ] ), } Defining the Pulse Sequence¶
Next, we will define the pulse sequence. We start from the start
object, which is an empty list of atom locations. In this case, we do not need atoms to build the pulse sequence, but to extract the sequence, we need to call the parse_sequence
method. This creates a Sequence
object that we can apply to multiple geometries.
In [3]: Copied! sequence = start.rydberg.rabi.amplitude.uniform.piecewise_linear(
+
distance = 4.0 inv_sqrt_2_rounded = 2.6 geometries = { 1: Chain(1), 2: Chain(2, lattice_spacing=distance), 3: start.add_position( [(-inv_sqrt_2_rounded, 0.0), (inv_sqrt_2_rounded, 0.0), (0, distance)] ), 4: Square(2, lattice_spacing=distance), 7: start.add_position( [ (0, 0), (distance, 0), (-0.5 * distance, distance), (0.5 * distance, distance), (1.5 * distance, distance), (0, 2 * distance), (distance, 2 * distance), ] ), } Defining the Pulse Sequence¶
Next, we will define the pulse sequence. We start from the start
object, which is an empty list of atom locations. In this case, we do not need atoms to build the pulse sequence, but to extract the sequence, we need to call the parse_sequence
method. This creates a Sequence
object that we can apply to multiple geometries.
In [3]: Copied! sequence = start.rydberg.rabi.amplitude.uniform.piecewise_linear(
durations=["ramp_time", "run_time", "ramp_time"],
values=[0.0, "rabi_drive", "rabi_drive", 0.0],
).parse_sequence()
-
sequence = start.rydberg.rabi.amplitude.uniform.piecewise_linear( durations=["ramp_time", "run_time", "ramp_time"], values=[0.0, "rabi_drive", "rabi_drive", 0.0], ).parse_sequence() Defining the Program¶
Now, all that is left to do is to compose the geometry and the Pulse sequence into a fully defined program. We can do this by calling the apply
method on the geometry and passing in the sequence. This method will return an object that can then be assigned parameters.
In [4]: Copied! batch = (
+
sequence = start.rydberg.rabi.amplitude.uniform.piecewise_linear( durations=["ramp_time", "run_time", "ramp_time"], values=[0.0, "rabi_drive", "rabi_drive", 0.0], ).parse_sequence() Defining the Program¶
Now, all that is left to do is to compose the geometry and the Pulse sequence into a fully defined program. We can do this by calling the apply
method on the geometry and passing in the sequence. This method will return an object that can then be assigned parameters.
In [4]: Copied! batch = (
geometries[7]
.apply(sequence)
.assign(ramp_time=0.06, rabi_drive=5)
.batch_assign(run_time=0.05 * np.arange(21))
)
-
batch = ( geometries[7] .apply(sequence) .assign(ramp_time=0.06, rabi_drive=5) .batch_assign(run_time=0.05 * np.arange(21)) ) Run Emulator and Hardware¶
Again, we run the program on the emulator and save the results to a file. for the emyulator and Aquila. Save the results to a file so that we can use them later.
In [5]: Copied! emu_filename = os.path.join(
+
batch = ( geometries[7] .apply(sequence) .assign(ramp_time=0.06, rabi_drive=5) .batch_assign(run_time=0.05 * np.arange(21)) ) Run Emulator and Hardware¶
Again, we run the program on the emulator and save the results to a file. for the emyulator and Aquila. Save the results to a file so that we can use them later.
In [5]: Copied! emu_filename = os.path.join(
os.path.abspath(""), "data", "multi-qubit-blockaded-emulation.json"
)
@@ -440,11 +440,11 @@
if not os.path.isfile(filename):
hardware_batch = batch.parallelize(24).braket.aquila().submit(shots=100)
save(hardware_batch, filename)
-
emu_filename = os.path.join( os.path.abspath(""), "data", "multi-qubit-blockaded-emulation.json" ) if not os.path.isfile(emu_filename): emu_batch = batch.braket.local_emulator().run(10000) save(emu_batch, emu_filename) filename = os.path.join(os.path.abspath(""), "data", "multi-qubit-blockaded-job.json") if not os.path.isfile(filename): hardware_batch = batch.parallelize(24).braket.aquila().submit(shots=100) save(hardware_batch, filename) Plotting the Results¶
First, we load the results from the file.
In [6]: Copied! emu_batch = load(emu_filename)
+
emu_filename = os.path.join( os.path.abspath(""), "data", "multi-qubit-blockaded-emulation.json" ) if not os.path.isfile(emu_filename): emu_batch = batch.braket.local_emulator().run(10000) save(emu_batch, emu_filename) filename = os.path.join(os.path.abspath(""), "data", "multi-qubit-blockaded-job.json") if not os.path.isfile(filename): hardware_batch = batch.parallelize(24).braket.aquila().submit(shots=100) save(hardware_batch, filename) Plotting the Results¶
First, we load the results from the file.
In [6]: Copied! emu_batch = load(emu_filename)
hardware_batch = load(filename)
# hardware_batch.fetch()
# save(filename, hardware_batch)
-
emu_batch = load(emu_filename) hardware_batch = load(filename) # hardware_batch.fetch() # save(filename, hardware_batch) The quantity of interest here is the total Rydberg density of the cluster defined as the sum of the Rydberg densities of each atom. We can extract this from the results and plot it as a function of time. We will do this for both the emulator and the hardware. We can use the rydberg_densities
function to extract the densities from the Report
of the batch
object.
In [7]: Copied! emu_report = emu_batch.report()
+
emu_batch = load(emu_filename) hardware_batch = load(filename) # hardware_batch.fetch() # save(filename, hardware_batch) The quantity of interest here is the total Rydberg density of the cluster defined as the sum of the Rydberg densities of each atom. We can extract this from the results and plot it as a function of time. We will do this for both the emulator and the hardware. We can use the rydberg_densities
function to extract the densities from the Report
of the batch
object.
In [7]: Copied! emu_report = emu_batch.report()
emu_densities = emu_report.rydberg_densities()
emu_densities_summed = emu_densities.sum(axis=1)
diff --git a/0.13.0/examples/example-2-nonequilibrium-dynamics-blockade-radius/index.html b/0.13.0/examples/example-2-nonequilibrium-dynamics-blockade-radius/index.html
index 16d3587..7670a69 100644
--- a/0.13.0/examples/example-2-nonequilibrium-dynamics-blockade-radius/index.html
+++ b/0.13.0/examples/example-2-nonequilibrium-dynamics-blockade-radius/index.html
@@ -386,7 +386,7 @@
display: inline-block;
white-space: normal;
}
- 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).
In [1]: Copied! from bloqade import save, load
+ 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).
In [1]: Copied! from bloqade import save, load
from bloqade.atom_arrangement import Chain
import numpy as np
@@ -396,7 +396,7 @@
if not os.path.isdir("data"):
os.mkdir("data")
-
from bloqade import save, load from bloqade.atom_arrangement import Chain import numpy as np import matplotlib.pyplot as plt import os if not os.path.isdir("data"): os.mkdir("data") Program Definition¶
We will start by defining a program. We set up a chain of two atoms with a parmaeterized distance between them. We then define a Rabi like in the original Rabi oscillation example. Given a rabi_ampl
of 15 rad/µs the blockaded radius s 8.44 µm. We will look at the dynamics of the system for a distance of 8.5 µm to be every so slightly outside of the blockade radius. We then define a batch
of programs for different run_time
values.
In [2]: Copied! initial_geometry = Chain(2, lattice_spacing="distance")
+
from bloqade import save, load from bloqade.atom_arrangement import Chain import numpy as np import matplotlib.pyplot as plt import os if not os.path.isdir("data"): os.mkdir("data") Program Definition¶
We will start by defining a program. We set up a chain of two atoms with a parmaeterized distance between them. We then define a Rabi like in the original Rabi oscillation example. Given a rabi_ampl
of 15 rad/µs the blockaded radius s 8.44 µm. We will look at the dynamics of the system for a distance of 8.5 µm to be every so slightly outside of the blockade radius. We then define a batch
of programs for different run_time
values.
In [2]: Copied! initial_geometry = Chain(2, lattice_spacing="distance")
program_waveforms = initial_geometry.rydberg.rabi.amplitude.uniform.piecewise_linear(
durations=["ramp_time", "run_time", "ramp_time"],
values=[0.0, "rabi_ampl", "rabi_ampl", 0.0],
@@ -405,20 +405,20 @@
ramp_time=0.06, rabi_ampl=15, distance=8.5
)
batch = program_assigned_vars.batch_assign(run_time=0.05 * np.arange(31))
-
initial_geometry = Chain(2, lattice_spacing="distance") program_waveforms = initial_geometry.rydberg.rabi.amplitude.uniform.piecewise_linear( durations=["ramp_time", "run_time", "ramp_time"], values=[0.0, "rabi_ampl", "rabi_ampl", 0.0], ) program_assigned_vars = program_waveforms.assign( ramp_time=0.06, rabi_ampl=15, distance=8.5 ) batch = program_assigned_vars.batch_assign(run_time=0.05 * np.arange(31)) Run Emulator and Hardware¶
Once again we will run the emulator and hardware. We will use the local_emulator
method to run the emulator locally. We will then save the results to a file so that we can use them later.
In [3]: Copied! emu_filename = os.path.join(
+
initial_geometry = Chain(2, lattice_spacing="distance") program_waveforms = initial_geometry.rydberg.rabi.amplitude.uniform.piecewise_linear( durations=["ramp_time", "run_time", "ramp_time"], values=[0.0, "rabi_ampl", "rabi_ampl", 0.0], ) program_assigned_vars = program_waveforms.assign( ramp_time=0.06, rabi_ampl=15, distance=8.5 ) batch = program_assigned_vars.batch_assign(run_time=0.05 * np.arange(31)) Run Emulator and Hardware¶
Once again we will run the emulator and hardware. We will use the local_emulator
method to run the emulator locally. We will then save the results to a file so that we can use them later.
In [3]: Copied! emu_filename = os.path.join(
os.path.abspath(""), "data", "nonequilibrium-dynamics-blockade-emulation.json"
)
if not os.path.isfile(emu_filename):
emu_batch = batch.braket.local_emulator().run(10000)
save(emu_batch, emu_filename)
-
emu_filename = os.path.join( os.path.abspath(""), "data", "nonequilibrium-dynamics-blockade-emulation.json" ) if not os.path.isfile(emu_filename): emu_batch = batch.braket.local_emulator().run(10000) save(emu_batch, emu_filename) When running on the hardware we will also parallelize the batch and submit.
In [4]: Copied! filename = os.path.join(
+
emu_filename = os.path.join( os.path.abspath(""), "data", "nonequilibrium-dynamics-blockade-emulation.json" ) if not os.path.isfile(emu_filename): emu_batch = batch.braket.local_emulator().run(10000) save(emu_batch, emu_filename) When running on the hardware we will also parallelize the batch and submit.
In [4]: Copied! filename = os.path.join(
os.path.abspath(""), "data", "nonequilibrium-dynamics-blockade-job.json"
)
if not os.path.isfile(filename):
hardware_batch = batch.parallelize(24).braket.aquila().run_async(shots=100)
save(hardware_batch, filename)
-
filename = os.path.join( os.path.abspath(""), "data", "nonequilibrium-dynamics-blockade-job.json" ) if not os.path.isfile(filename): hardware_batch = batch.parallelize(24).braket.aquila().run_async(shots=100) save(hardware_batch, filename) Plotting the Results¶
In order to show the complex dynamics we will plot the probability of having 0
, 1
, or 2
Rydberg atoms as a function of time. We will do this for both the emulator and the hardware. We can use the rydberg_state_probabilities
function to extract the probabilities from the counts. This function takes a list of counts and returns a dictionary of probabilities for each state. The counts are obtained from the report
of the batch
object.
In [5]: Copied! def rydberg_state_probabilities(shot_counts):
+
filename = os.path.join( os.path.abspath(""), "data", "nonequilibrium-dynamics-blockade-job.json" ) if not os.path.isfile(filename): hardware_batch = batch.parallelize(24).braket.aquila().run_async(shots=100) save(hardware_batch, filename) Plotting the Results¶
In order to show the complex dynamics we will plot the probability of having 0
, 1
, or 2
Rydberg atoms as a function of time. We will do this for both the emulator and the hardware. We can use the rydberg_state_probabilities
function to extract the probabilities from the counts. This function takes a list of counts and returns a dictionary of probabilities for each state. The counts are obtained from the report
of the batch
object.
In [5]: Copied! def rydberg_state_probabilities(shot_counts):
probabilities_dict = {"0": [], "1": [], "2": []}
# iterate over each of the task results
@@ -433,20 +433,20 @@
probabilities_dict["2"].append(task_result.get("00", 0) / total_shots)
return probabilities_dict
-
def rydberg_state_probabilities(shot_counts): probabilities_dict = {"0": [], "1": [], "2": []} # iterate over each of the task results for task_result in shot_counts: # get total number of shots total_shots = sum(task_result.values()) # get probability of each state probabilities_dict["0"].append(task_result.get("11", 0) / total_shots) probabilities_dict["1"].append( (task_result.get("10", 0) + task_result.get("01", 0)) / total_shots ) probabilities_dict["2"].append(task_result.get("00", 0) / total_shots) return probabilities_dict Extracting the counts and probabilities¶
We will now extract the counts and probabilities from the emulator and hardware runs. We will then plot the results. First we load the data from the files.
In [6]: Copied! emu_batch = load(emu_filename)
+
def rydberg_state_probabilities(shot_counts): probabilities_dict = {"0": [], "1": [], "2": []} # iterate over each of the task results for task_result in shot_counts: # get total number of shots total_shots = sum(task_result.values()) # get probability of each state probabilities_dict["0"].append(task_result.get("11", 0) / total_shots) probabilities_dict["1"].append( (task_result.get("10", 0) + task_result.get("01", 0)) / total_shots ) probabilities_dict["2"].append(task_result.get("00", 0) / total_shots) return probabilities_dict Extracting the counts and probabilities¶
We will now extract the counts and probabilities from the emulator and hardware runs. We will then plot the results. First we load the data from the files.
In [6]: Copied! emu_batch = load(emu_filename)
emu_report = emu_batch.report()
emu_counts = emu_report.counts()
hardware_batch = load(filename)
# hardware_batch.fetch() # uncomment to fetch results from Braket
# save(filename, hardware_batch)
-
emu_batch = load(emu_filename) emu_report = emu_batch.report() emu_counts = emu_report.counts() hardware_batch = load(filename) # hardware_batch.fetch() # uncomment to fetch results from Braket # save(filename, hardware_batch) To get the counts
we need to get a report
from the batch
objects. Then with the report we can get the counts. The counts are a dictionary that maps the bitstring to the number of times that bitstring was measured.
In [7]: Copied! emu_report = emu_batch.report()
+
emu_batch = load(emu_filename) emu_report = emu_batch.report() emu_counts = emu_report.counts() hardware_batch = load(filename) # hardware_batch.fetch() # uncomment to fetch results from Braket # save(filename, hardware_batch) To get the counts
we need to get a report
from the batch
objects. Then with the report we can get the counts. The counts are a dictionary that maps the bitstring to the number of times that bitstring was measured.
In [7]: Copied! emu_report = emu_batch.report()
hardware_report = hardware_batch.report()
emu_rydberg_state_probabilities = rydberg_state_probabilities(emu_report.counts())
hw_rydberg_state_probabilities = rydberg_state_probabilities(hardware_report.counts())
-
emu_report = emu_batch.report() hardware_report = hardware_batch.report() emu_rydberg_state_probabilities = rydberg_state_probabilities(emu_report.counts()) hw_rydberg_state_probabilities = rydberg_state_probabilities(hardware_report.counts()) plot 0, 1, and 2 Rydberg state probabilities but in separate plots
In [8]: Copied! figure, axs = plt.subplots(1, 3, figsize=(12, 6), sharey=True)
+
emu_report = emu_batch.report() hardware_report = hardware_batch.report() emu_rydberg_state_probabilities = rydberg_state_probabilities(emu_report.counts()) hw_rydberg_state_probabilities = rydberg_state_probabilities(hardware_report.counts()) plot 0, 1, and 2 Rydberg state probabilities but in separate plots
In [8]: Copied! figure, axs = plt.subplots(1, 3, figsize=(12, 6), sharey=True)
emu_run_times = emu_report.list_param("run_time")
hardware_run_times = hardware_report.list_param("run_time")
diff --git a/0.13.0/examples/example-2-two-qubit-adiabatic/index.html b/0.13.0/examples/example-2-two-qubit-adiabatic/index.html
index c26fe8e..5d650ef 100644
--- a/0.13.0/examples/example-2-two-qubit-adiabatic/index.html
+++ b/0.13.0/examples/example-2-two-qubit-adiabatic/index.html
@@ -386,7 +386,7 @@
display: inline-block;
white-space: normal;
}
- 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).
Two Qubit Adiabatic Sweep¶
Introduction¶
In this example, we show how to use Bloqade to program an adiabatic sweep on a pair of atoms, with the distance between atoms gradually increasing per task. This will allow us to explore the effect of the Rydberg interaction. We will run the program on both the emulator and the hardware to compare the results.
In [1]: Copied! from bloqade import start, cast, var, save, load
+ 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).
Two Qubit Adiabatic Sweep¶
Introduction¶
In this example, we show how to use Bloqade to program an adiabatic sweep on a pair of atoms, with the distance between atoms gradually increasing per task. This will allow us to explore the effect of the Rydberg interaction. We will run the program on both the emulator and the hardware to compare the results.
In [1]: Copied! from bloqade import start, cast, var, save, load
import numpy as np
import matplotlib.pyplot as plt
@@ -395,7 +395,7 @@
if not os.path.isdir("data"):
os.mkdir("data")
-
from bloqade import start, cast, var, save, load import numpy as np import matplotlib.pyplot as plt import os if not os.path.isdir("data"): os.mkdir("data") Defining the Program¶
Now, we define our program of interest. For an adiabatic protocol, we keep that Rabi frequency at a considerable value while slowly ramping the detuning from a large negative to a positive value. The idea is that when the detuning is large and negative the atoms remain in the ground state. As the detuning is ramped to positive values, the atoms are able to be excited to the Rydberg state, however if the atoms are too close together, the Rydberg interactions effectively acts like a negative etuning to neighboring atoms, preventing them from being excited. This is the blockade effect. For atoms that are sufficiently far apart, the Rydberg interaction is negligible and the atoms can be excited to the Rydberg state. As the atoms get closer together, the Rydberg interaction becomes more significant the probability of exciting both atoms becomes smaller. The typical length scale for the cross over from the non-interacting to the blockade regime is the blockade radius.
Note that you can perform arithmetic operations directly on variables in the program but this requires the variable to be explicitly declared by passing a string to the var
function and THEN doing arithmetic on it.
In [2]: Copied! detuning_value = var("detuning_value")
+
from bloqade import start, cast, var, save, load import numpy as np import matplotlib.pyplot as plt import os if not os.path.isdir("data"): os.mkdir("data") Defining the Program¶
Now, we define our program of interest. For an adiabatic protocol, we keep that Rabi frequency at a considerable value while slowly ramping the detuning from a large negative to a positive value. The idea is that when the detuning is large and negative the atoms remain in the ground state. As the detuning is ramped to positive values, the atoms are able to be excited to the Rydberg state, however if the atoms are too close together, the Rydberg interactions effectively acts like a negative etuning to neighboring atoms, preventing them from being excited. This is the blockade effect. For atoms that are sufficiently far apart, the Rydberg interaction is negligible and the atoms can be excited to the Rydberg state. As the atoms get closer together, the Rydberg interaction becomes more significant the probability of exciting both atoms becomes smaller. The typical length scale for the cross over from the non-interacting to the blockade regime is the blockade radius.
Note that you can perform arithmetic operations directly on variables in the program but this requires the variable to be explicitly declared by passing a string to the var
function and THEN doing arithmetic on it.
In [2]: Copied! detuning_value = var("detuning_value")
durations = cast(["ramp_time", "run_time", "ramp_time"])
prog = (
start.add_position([(0, 0), (0, "atom_distance")])
@@ -417,7 +417,7 @@
batch = prog.assign(
ramp_time=1.0, run_time=2.0, rabi_value=15.0, detuning_value=15.0
).batch_assign(atom_distance=distances)
-
detuning_value = var("detuning_value") durations = cast(["ramp_time", "run_time", "ramp_time"]) prog = ( start.add_position([(0, 0), (0, "atom_distance")]) .rydberg.rabi.amplitude.uniform.piecewise_linear( durations=durations, values=[0, "rabi_value", "rabi_value", 0] ) .detuning.uniform.piecewise_linear( durations=durations, values=[ -detuning_value, -detuning_value, detuning_value, detuning_value, ], ) ) distances = np.arange(4, 11, 1) batch = prog.assign( ramp_time=1.0, run_time=2.0, rabi_value=15.0, detuning_value=15.0 ).batch_assign(atom_distance=distances) Run on Emulator and Hardware¶
In previous examples, we have shown how to run a program on the emulator and hardware. First, we will run the program on the emulator and save the results to a file.
In [3]: Copied! # get emulation batch, running 1000 shots per task
+
detuning_value = var("detuning_value") durations = cast(["ramp_time", "run_time", "ramp_time"]) prog = ( start.add_position([(0, 0), (0, "atom_distance")]) .rydberg.rabi.amplitude.uniform.piecewise_linear( durations=durations, values=[0, "rabi_value", "rabi_value", 0] ) .detuning.uniform.piecewise_linear( durations=durations, values=[ -detuning_value, -detuning_value, detuning_value, detuning_value, ], ) ) distances = np.arange(4, 11, 1) batch = prog.assign( ramp_time=1.0, run_time=2.0, rabi_value=15.0, detuning_value=15.0 ).batch_assign(atom_distance=distances) Run on Emulator and Hardware¶
In previous examples, we have shown how to run a program on the emulator and hardware. First, we will run the program on the emulator and save the results to a file.
In [3]: Copied! # get emulation batch, running 1000 shots per task
emu_filename = os.path.join(
os.path.abspath(""), "data", "two-qubit-adiabatic-emulation.json"
)
@@ -425,12 +425,12 @@
if not os.path.isfile(emu_filename):
emu_batch = batch.braket.local_emulator().run(1000)
save(emu_batch, emu_filename)
-
# get emulation batch, running 1000 shots per task emu_filename = os.path.join( os.path.abspath(""), "data", "two-qubit-adiabatic-emulation.json" ) if not os.path.isfile(emu_filename): emu_batch = batch.braket.local_emulator().run(1000) save(emu_batch, emu_filename) Then, we can run the program on the hardware after parallelizing the tasks. We can then save the results to a file.
In [4]: Copied! filename = os.path.join(os.path.abspath(""), "data", "two-qubit-adiabatic-job.json")
+
# get emulation batch, running 1000 shots per task emu_filename = os.path.join( os.path.abspath(""), "data", "two-qubit-adiabatic-emulation.json" ) if not os.path.isfile(emu_filename): emu_batch = batch.braket.local_emulator().run(1000) save(emu_batch, emu_filename) Then, we can run the program on the hardware after parallelizing the tasks. We can then save the results to a file.
In [4]: Copied! filename = os.path.join(os.path.abspath(""), "data", "two-qubit-adiabatic-job.json")
if not os.path.isfile(filename):
hardware_batch = batch.parallelize(24).braket.aquila().submit(shots=100)
save(hardware_batch, filename)
-
filename = os.path.join(os.path.abspath(""), "data", "two-qubit-adiabatic-job.json") if not os.path.isfile(filename): hardware_batch = batch.parallelize(24).braket.aquila().submit(shots=100) save(hardware_batch, filename) Plot the Results¶
To show the blockade effect on the system, we will plot the probability of having 0
, 1
, or 2
Rydberg atoms as a function of time. We will do this for both the emulator and the hardware. We can use the following function to get the probabilities from the shot counts of each of the different configurations of the two Rydberg atoms: 00
, 10
, 01
, and 11
. Note that 0
corresponds to the Rydberg state while 1
corresponds to the ground state. As such, 00
corresponds to two Rydberg atoms, 10
and 01
corresponds to one Rydberg atom and one ground-state atom, and 11
corresponds to two ground-state atoms.
In [5]: Copied! def rydberg_state_probabilities(emu_counts):
+
filename = os.path.join(os.path.abspath(""), "data", "two-qubit-adiabatic-job.json") if not os.path.isfile(filename): hardware_batch = batch.parallelize(24).braket.aquila().submit(shots=100) save(hardware_batch, filename) Plot the Results¶
To show the blockade effect on the system, we will plot the probability of having 0
, 1
, or 2
Rydberg atoms as a function of time. We will do this for both the emulator and the hardware. We can use the following function to get the probabilities from the shot counts of each of the different configurations of the two Rydberg atoms: 00
, 10
, 01
, and 11
. Note that 0
corresponds to the Rydberg state while 1
corresponds to the ground state. As such, 00
corresponds to two Rydberg atoms, 10
and 01
corresponds to one Rydberg atom and one ground-state atom, and 11
corresponds to two ground-state atoms.
In [5]: Copied! def rydberg_state_probabilities(emu_counts):
probabilities_dict = {"0": [], "1": [], "2": []}
# iterate over each of the task results
@@ -445,14 +445,14 @@
probabilities_dict["2"].append(task_result.get("00", 0) / total_shots)
return probabilities_dict
-
def rydberg_state_probabilities(emu_counts): probabilities_dict = {"0": [], "1": [], "2": []} # iterate over each of the task results for task_result in emu_counts: # get total number of shots total_shots = sum(task_result.values()) # get probability of each state probabilities_dict["0"].append(task_result.get("11", 0) / total_shots) probabilities_dict["1"].append( (task_result.get("10", 0) + task_result.get("01", 0)) / total_shots ) probabilities_dict["2"].append(task_result.get("00", 0) / total_shots) return probabilities_dict Before we can plot the results we need to load the data from the files.
In [6]: Copied! # get emulation report and number of shots per each state
+
def rydberg_state_probabilities(emu_counts): probabilities_dict = {"0": [], "1": [], "2": []} # iterate over each of the task results for task_result in emu_counts: # get total number of shots total_shots = sum(task_result.values()) # get probability of each state probabilities_dict["0"].append(task_result.get("11", 0) / total_shots) probabilities_dict["1"].append( (task_result.get("10", 0) + task_result.get("01", 0)) / total_shots ) probabilities_dict["2"].append(task_result.get("00", 0) / total_shots) return probabilities_dict Before we can plot the results we need to load the data from the files.
In [6]: Copied! # get emulation report and number of shots per each state
emu_batch = load(emu_filename)
# get hardware report and number of shots per each state
hardware_batch = load(filename)
# hardware_batch.fetch()
# save(hardware_batch, filename)
-
# get emulation report and number of shots per each state emu_batch = load(emu_filename) # get hardware report and number of shots per each state hardware_batch = load(filename) # hardware_batch.fetch() # save(hardware_batch, filename) We can use the rydberg_state_probabilities
function to extract the probabilities from the counts. This function takes a list of counts and returns a dictionary of probabilities for each state. The counts are obtained from the report
of the batch
object.
Now, we can plot the results!
In [7]: Copied! emu_report = emu_batch.report()
+
# get emulation report and number of shots per each state emu_batch = load(emu_filename) # get hardware report and number of shots per each state hardware_batch = load(filename) # hardware_batch.fetch() # save(hardware_batch, filename) We can use the rydberg_state_probabilities
function to extract the probabilities from the counts. This function takes a list of counts and returns a dictionary of probabilities for each state. The counts are obtained from the report
of the batch
object.
Now, we can plot the results!
In [7]: Copied! emu_report = emu_batch.report()
hardware_report = hardware_batch.report()
emu_rydberg_state_probabilities = rydberg_state_probabilities(emu_report.counts())
diff --git a/0.13.0/examples/example-3-2d-ordered-state/index.html b/0.13.0/examples/example-3-2d-ordered-state/index.html
index e6afc8a..bd7b9ec 100644
--- a/0.13.0/examples/example-3-2d-ordered-state/index.html
+++ b/0.13.0/examples/example-3-2d-ordered-state/index.html
@@ -386,7 +386,7 @@
display: inline-block;
white-space: normal;
}
- 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).
You might notice that the tools we need to import are a lot shorter than prior instances. This is because we're taking advantage of bloqade Python's built-in visualization capabilities instead of crafting a new plot with matplotlib.
In [1]: Copied! from bloqade.atom_arrangement import Square
+ 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).
You might notice that the tools we need to import are a lot shorter than prior instances. This is because we're taking advantage of bloqade Python's built-in visualization capabilities instead of crafting a new plot with matplotlib.
In [1]: Copied! from bloqade.atom_arrangement import Square
from bloqade import save, load
from bokeh.io import output_notebook
@@ -405,8 +405,8 @@
height: 20px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAOkSURBVDiNjZRtaJVlGMd/1/08zzln5zjP1LWcU9N0NkN8m2CYjpgQYQXqSs0I84OLIC0hkEKoPtiH3gmKoiJDU7QpLgoLjLIQCpEsNJ1vqUOdO7ppbuec5+V+rj4ctwzd8IIbbi6u+8f1539dt3A78eXC7QizUF7gyV1fD1Yqg4JWz84yffhm0qkFqBogB9rM8tZdtwVsPUhWhGcFJngGeWrPzHm5oaMmkfEg1usvLFyc8jLRqDOMru7AyC8saQr7GG7f5fvDeH7Ej8CM66nIF+8yngt6HWaKh7k49Soy9nXurCi1o3qUbS3zWfrYeQDTB/Qj6kX6Ybhw4B+bOYoLKCC9H3Nu/leUTZ1JdRWkkn2ldcCamzrcf47KKXdAJllSlxAOkRgyHsGC/zRday5Qld9DyoM4/q/rUoy/CXh3jzOu3bHUVZeU+DEn8FInkPBFlu3+nW3Nw0mk6vCDiWg8CeJaxEwuHS3+z5RgY+YBR6V1Z1nxSOfoaPa4LASWxxdNp+VWTk7+4vzaou8v8PN+xo+KY2xsw6une2frhw05CTYOmQvsEhjhWjn0bmXPjpE1+kplmmkP3suftwTubK9Vq22qKmrBhpY4jvd5afdRA3wGjFAgcnTK2s4hY0/GPNIb0nErGMCRxWOOX64Z8RAC4oCXdklmEvcL8o0BfkNK4lUg9HTl+oPlQxdNo3Mg4Nv175e/1LDGzZen30MEjRUtmXSfiTVu1kK8W4txyV6BMKlbgk3lMwYCiusNy9fVfvvwMxv8Ynl6vxoByANLTWplvuj/nF9m2+PDtt1eiHPBr1oIfhCChQMBw6Aw0UulqTKZdfVvfG7VcfIqLG9bcldL/+pdWTLxLUy8Qq38heUIjh4XlzZxzQm19lLFlr8vdQ97rjZVOLf8nclzckbcD4wxXMidpX30sFd37Fv/GtwwhzhxGVAprjbg0gCAEeIgwCZyTV2Z1REEW8O4py0wsjeloKoMr6iCY6dP92H6Vw/oTyICIthibxjm/DfN9lVz8IqtqKYLUXfoKVMVQVVJOElGjrnnUt9T9wbgp8AyYKaGlqingHZU/uG2NTZSVqwHQTWkx9hxjkpWDaCg6Ckj5qebgBVbT3V3NNXMSiWSDdGV3hrtzla7J+duwPOToIg42ChPQOQjspnSlp1V+Gjdged7+8UN5CRAV7a5EdFNwCjEaBR27b3W890TE7g24NAP/mMDXRWrGoFPQI9ls/MWO2dWFAar/xcOIImbbpA3zgAAAABJRU5ErkJggg==);
}
-
Program Definition¶
We define a program where our geometry is a square lattice of 3x3 atoms. Notice that unlke the 1D Z2 state preparation example the detuning now ramps to a higher value and the atoms are closer together.
In [2]: Copied! # Have atoms separated by 5.9 micrometers
+
Program Definition¶
We define a program where our geometry is a square lattice of 3x3 atoms. Notice that unlke the 1D Z2 state preparation example the detuning now ramps to a higher value and the atoms are closer together.
In [2]: Copied! # Have atoms separated by 5.9 micrometers
L = 3
lattice_spacing = 5.9
@@ -700,7 +700,7 @@
)
batch = prog.assign(delta_end=42.66, sweep_time=2.4)
-
# Have atoms separated by 5.9 micrometers L = 3 lattice_spacing = 5.9 rabi_amplitude_values = [0.0, 15.8, 15.8, 0.0] rabi_detuning_values = [-16.33, -16.33, "delta_end", "delta_end"] durations = [0.8, "sweep_time", 0.8] prog = ( Square(L, lattice_spacing=lattice_spacing) .rydberg.rabi.amplitude.uniform.piecewise_linear(durations, rabi_amplitude_values) .detuning.uniform.piecewise_linear(durations, rabi_detuning_values) ) batch = prog.assign(delta_end=42.66, sweep_time=2.4) Submitting to Emulator and Hardware¶
Just as in prior examples, we submit our program to both hardware and the emulator and save the intermediate data in files for convenient fetching when the results are ready from hardware, as well as avoiding having to repeat emulation runs for the purposes of analysis.
Considering how small a 3 x 3 lattice of atoms is relative to machine capabilities, we also take advantage of parallelization to duplicate the geometry and get more data per shot when submitting to Hardware.
In [3]: Copied! emu_filename = os.path.join(
+
# Have atoms separated by 5.9 micrometers L = 3 lattice_spacing = 5.9 rabi_amplitude_values = [0.0, 15.8, 15.8, 0.0] rabi_detuning_values = [-16.33, -16.33, "delta_end", "delta_end"] durations = [0.8, "sweep_time", 0.8] prog = ( Square(L, lattice_spacing=lattice_spacing) .rydberg.rabi.amplitude.uniform.piecewise_linear(durations, rabi_amplitude_values) .detuning.uniform.piecewise_linear(durations, rabi_detuning_values) ) batch = prog.assign(delta_end=42.66, sweep_time=2.4) Submitting to Emulator and Hardware¶
Just as in prior examples, we submit our program to both hardware and the emulator and save the intermediate data in files for convenient fetching when the results are ready from hardware, as well as avoiding having to repeat emulation runs for the purposes of analysis.
Considering how small a 3 x 3 lattice of atoms is relative to machine capabilities, we also take advantage of parallelization to duplicate the geometry and get more data per shot when submitting to Hardware.
In [3]: Copied! emu_filename = os.path.join(
os.path.abspath(""), "data", "striated-phase-emulation.json"
)
if not os.path.isfile(emu_filename):
@@ -711,20 +711,20 @@
if not os.path.isfile(hw_filename):
future = batch.parallelize(24).braket.aquila().run_async(shots=100)
save(future, hw_filename)
-
emu_filename = os.path.join( os.path.abspath(""), "data", "striated-phase-emulation.json" ) if not os.path.isfile(emu_filename): emu_future = batch.braket.local_emulator().run(shots=10000) save(emu_future, emu_filename) hw_filename = os.path.join(os.path.abspath(""), "data", "striated-phase-hardware.json") if not os.path.isfile(hw_filename): future = batch.parallelize(24).braket.aquila().run_async(shots=100) save(future, hw_filename) Extracting Results¶
We can reload our files to get results:
In [4]: Copied! # retrieve results from emulator and HW
+
emu_filename = os.path.join( os.path.abspath(""), "data", "striated-phase-emulation.json" ) if not os.path.isfile(emu_filename): emu_future = batch.braket.local_emulator().run(shots=10000) save(emu_future, emu_filename) hw_filename = os.path.join(os.path.abspath(""), "data", "striated-phase-hardware.json") if not os.path.isfile(hw_filename): future = batch.parallelize(24).braket.aquila().run_async(shots=100) save(future, hw_filename) Extracting Results¶
We can reload our files to get results:
In [4]: Copied! # retrieve results from emulator and HW
emu_batch = load(emu_filename)
hardware_batch = load(hw_filename)
# Uncomment lines below to fetch results from Braket
# hardware_batch = hardware_batch.fetch()
# save(hardware_batch, filename)
-
# retrieve results from emulator and HW emu_batch = load(emu_filename) hardware_batch = load(hw_filename) # Uncomment lines below to fetch results from Braket # hardware_batch = hardware_batch.fetch() # save(hardware_batch, filename) Visualizing Results With Ease¶
In prior examples we've leverage Bloqade's ability to automatically put hardware and emulation results into the formats we need to make analysis easier.
Now we'll go one step further by letting Bloqade Python do the visualization for us. In this case we'll visualize the Rydberg Densities of our system overlaid on the original geometry with just the following:
In [5]: Copied! emu_batch.report().show()
-
emu_batch.report().show() Just as before, we let Bloqade generate a report
which contains all the results in easy to digest format but we invoke the .show()
method of our report which us easily get an idea of the results of our experiment with interactive plots.
The plot that mos interests us is the one on the right under the "Rydberg Density" section.
In [6]: Copied! hardware_batch.report().show()
-
hardware_batch.report().show() Just as before, we let Bloqade generate a report
which contains all the results in easy to digest format but we invoke the .show()
method of our report which us easily get an idea of the results of our experiment with interactive plots.
The plot that mos interests us is the one on the right under the "Rydberg Density" section.
In [6]: Copied! hardware_batch.report().show()
+
hardware_batch.report().show() Considering Bloqade's goal of a uniform visualization pipeline, we can get the same ability for results from hardware. Note that we can confirm the program does what it's supposed to as results from emulation agree with those from hardware quite well.