Walkthrough 4: Optical landscapes¶

Introduction¶

While Barrier objects explored in the last walkthrough are quite powerful, there are instances where the limits imposed on the user by this abstraction are not flexible enough (e.g., the barrier shapes are pre-determined, etc.). In this walkthrough, we explore a more customizable way to alter the potential energy, as a function of position, experienced by the atom ensemble during the manipulation phase. Our new objects consist of Snapshots , aka snapshots, which are optical potentials specified by providing a spatially-dependent potential energy at given positions and a fixed time during the manipulation phase. Dynamics are supported by allowing the user to specify many such snapshots, each with a unique time, along with control over how those snapshots are connected/interpolated point-by-point. This construction is called a Landscape and is the focus of this walkthrough.

NOTE: Landscape-type potentials are sourced from the same laser light as barriers and represent another abstraction available for the user to explore the "painted light" capabilities of Oqtant hardware.

Imports and user authentication¶

In [ ]:
from oqtant.oqtant_client import get_oqtant_client
from oqtant.util.auth import notebook_login
from oqtant.schemas.quantum_matter import QuantumMatterFactory as qmf

oqtant_account = notebook_login()
oqtant_account
In [ ]:
client = get_oqtant_client(oqtant_account.access_token)

Snapshot objects¶

Snapshot objects are the basis of our new abstraction. They represent the desired instantaneous optical potential applied to the atom ensemble at a particular time. Here, we will often refer to them only as snapshots.

Object creation¶

We can construct such a snapshot by passing equal length lists of positions (in microns) and corresponding potentials (in kHz), the time (in ms) at which this snapshot should be realized, and an interpolation parameter that communicates how to connect the given data in space.

In [ ]:
snapshot = qmf.create_snapshot(
    time=2,
    positions=[-10, -5, 0, 5, 10],
    potentials=[0, 10, 20, 15, 0],
    interpolation="LINEAR",
)

Visualizing the instantaneous potential energy contribution¶

Much like for barriers, we can use the Snapshot.show_potential() method to visualize the potential energy contribution of a particular snapshot:

In [ ]:
snapshot.show_potential()

We can see the underlying data used to instantiate the landscape object (orange points) and how the potential energy at other positions will be calculated based on that data (blue line) according to the interpolation choice.

Spatial interpolation options¶

The interpolation parameter passed to the constructor of our snapshot object controls how the given points formed by the equal length lists of (positions, potentials) are connected spatially (as opposed to the previously encountered rf evaporation and barrier interpolation inputs, which controlled how points were connected in time.) Options for spatial interpolation include those options familiar to users of the scipy library and are summarized in the following table:

Interpolation parameter value notes
"ZERO" Spline interpolation at zeroth order
"SLINEAR" Spline interpolation at first order
"QUADRATIC" Spline interpolation at second order
"CUBIC" or "SMOOTH" Spline interpolation at third order
"OFF" or "STEP" or "PREVIOUS" Assumes value of previous data point
"NEXT" Assumes value of next data point
"LINEAR" Linear interpolation between points

Note: The total optical potential applied to the quantum matter sample is bounded below by 0 kHz (no painted light at that position) and above by 100 kHz, which under certain circumstances can alter the expected potential energy landscape. This is particularly true for high-order interpolation options, e.g. cubic, which tend to overshoot or undershoot these bounds for points close in proximity to them. Also, just as for multiple barriers that overlap, the presence of snapshots/landscapes and barriers together can lead to optical potentials that sample this energetic ceiling.

In [ ]:
options = ["OFF", "LINEAR", "CUBIC"]
for option in options:
    snapshot = qmf.create_snapshot(
        time=2,
        positions=[-10, -5, 0, 5, 10, 25, 30, 39],
        potentials=[0, 10, 20, 15, 0, 10, 15, 2],
        interpolation=option,
    )
    snapshot.show_potential()

As shown, the snapshot data structure allows for very flexible potential-energy profiles.

Landscape objects¶

Landscape objects represent the dynamic potential energy as a function of position realized by connecting a series of snapshots together in time. A valid landscape needs at least two constituent snapshots to define the potential at, in this case, the start and end time that the landscape should be applied. At intermediate times between the provided snapshots, the overall landscape / potential energy as a function of position is linearly interpolated point-by-point (in position). This interpolation behavior is not user configurable.

NOTE: The time values of individual snapshots that make up the overall Landscape object must be at least 1 ms apart.

Let us demonstrate the instantiation of an landscape that evolves between a narrow/short barrier-like object to a wider/taller one with a different spatial interpolation style.

In [ ]:
snapshot1 = qmf.create_snapshot(
    time=2,
    positions=[-10, -5, 0, 5, 10],
    potentials=[0, 10, 20, 15, 0],
    interpolation="LINEAR",
)

snapshot2 = qmf.create_snapshot(
    time=5,
    positions=[-20, -10, 0, 10, 20],
    potentials=[0, 15, 40, 10, 0],
    interpolation="CUBIC",
)

landscape = qmf.create_landscape(snapshots=[snapshot2, snapshot1])

We can observe this applied potential energy derived from our landscape object at any particular time using the Landscape.show_potential() method:

In [ ]:
landscape.show_potential(times=[2, 3, 4, 5])

Adding a Landscape object to QuantumMatter¶

Similar to how we added barriers, we can also add landscapes to our quantum matter objects, in this case by providing a landscape parameter during instantiation. In the below example, we include both a simple Barrier as well as our new Landscape:

In [ ]:
# define a simple barrier, just as an example, that lives until t = 13
barrier = qmf.create_barrier(
    position=30, height=30, width=3, shape="GAUSSIAN", birth=3, lifetime=7
)
barrier.evolve(duration=3, height=15, position=-30)

# and the dynamic landscape, consisting of two snapshots for this example
snapshot1 = qmf.create_snapshot(
    time=0,
    positions=[-10, -5, 0, 5, 10],
    potentials=[0, 10, 20, 15, 0],
    interpolation="LINEAR",
)
snapshot2 = qmf.create_snapshot(
    time=15,
    positions=[-20, -10, 0, 10, 20],
    potentials=[0, 15, 40, 10, 0],
    interpolation="LINEAR",
)
landscape = qmf.create_landscape(snapshots=[snapshot1, snapshot2])

# and construct the program
matter = qmf.create_quantum_matter(
    name="paint 1d job trial",
    temperature=100,
    lifetime=20,
    time_of_flight=10,
    barriers=[barrier],
    landscape=landscape,
)

Visualizing the overall potential energy¶

Just as in our examples in previous walkthroughs that did not include the optional landscape parameter, we can show the total spatial potential energy as a function of position for various manipulation phase times using the QuantumMatter.show_potential() method. The total potential will include contributions from both our QuantumMatter object's constituent barriers, the new landscape, as well as the background magnetic trapping field:

In [ ]:
matter.show_potential(times=[2, 4, 8, 16])

As shown, the total potential includes both the evolving landscape as well as the scanning barrier (in addition to the magnetic trapping potential).

Submitting to QMS¶

In [ ]:
my_job_id = client.submit(matter, track=True)

Retrieving results¶

We retrieve the results of our job in the normal way:

In [ ]:
my_job = client.get_job(my_job_id)

The output data include the same results seen previously for jobs that use time-of-flight imaging. Despite the presence of the painted potentials, the Oqtant platform will still attempt to fit a temperature to the falling cloud. Due to the high chance of additional interference or structure in this image, these results will be far less reliable. Advanced analysis is left to the user.

In [ ]:
print(my_job.output_fields)
In [ ]:
my_job.plot_tof(figsize=(6, 6))

Advanced options and discussion¶

Mapped job type¶

Our QuantumMatter object that includes a non-null input for the landscape exceeds the capabilities of an Oqtant BARRIER job. Instead, it gets mapped to a PAINT_1D job type. We can double check this by evaluating the following:

In [ ]:
print(my_job.job_type)