Getting started: setting up your first quantum simulation problem¶
To get familiar with the main concepts of QruiseML, you can work through this example notebook. We'll briefly explain how to define a drive and Hamiltonian, set up and solve a quantum problem, and plot the results.
Defining the Hamiltonian¶
We describe the dynamics of a quantum system using a Hamiltonian of the form
$$ H_{\text{total}} (t; p) = H_0 + H(t; p), $$where $H_0$ and $H(t, p)$ are the stationary and time-dependent parts, respectively, and $p$ represents the drive parameter set $p$.
Time-dependent drive functions¶
The time-dependent part is constructed by combining drive functions with quantum operators. A drive function is a time-dependent control field applied to the system, which determines the evolution of the quantum state.
For example, we could use a sinusoidal drive of the form
$$ f(t; \{a, \omega, \phi\}) = a \sin(\omega t + \phi), $$where $a$ is the amplitude, $\omega$ the frequency, and $\phi$ the phase.
In QruiseML, drive functions are defined as annotated Python functions. This means that each input parameter, as well as the function output, must have a type annotation. These annotations must use the types defined in the types module in QruiseML.
For example, we can define a sine drive and a cosine drive, where the inputs and the output are all 64-bit floating-point numbers:
import numpy as np
from qruise.toolset.types import Float64
# define sine drive
def f(t: Float64, a: Float64, w: Float64, p: Float64) -> Float64:
return a * np.sin(w * t + p)
# define cosine drive
def g(t: Float64, a: Float64, w: Float64, p: Float64) -> Float64:
return a * np.cos(w * t + p)
Drive parameters and channels¶
Next, we define the parameter values associated with each drive function.
When working with multiple drives in QruiseML, we need to specify which parameters belong to which drive channel. This ensures that parameters with the same name — such as a, w, and p — are associated with the correct drive channel. We can do this by creating a Parameters object with two drive channels, "d1" and "d2":
from qruise.toolset import Parameters
ps = Parameters(
{"d1": {"a": 1.0, "w": 2.0, "p": 0.01}, "d2": {"a": 0.5, "w": 1.8, "p": -0.3}}
)
We then use the Drive class to associate the parameters of each drive function (f and g) with their corresponding channel ("d1" and "d2"):
from qruise.toolset import Drive
drv1 = Drive("d1", f)
drv2 = Drive("d2", g)
Constructing the Hamiltonian¶
The Hamiltonian is constructed in a similar way to QuTiP. We define the stationary part, H0, and the time-dependent part, which consists of pairs of drives ($d_n$) and quantum operators ($A_n$), e.g.
Let's construct the Hamiltonian
$$ H_{\text{total}} (t; p) = \sigma_z + d_1 \sigma_x + d_2 \sigma_y, $$where $\sigma_{x,y,z}$ are Pauli operators. We instantiate this using the Hamiltonian class by passing the stationary part of the Hamiltonian together with a list
of (drive, operator) pairs defining the time-dependent terms:
import qutip as qt
from qruise.toolset import Hamiltonian
# define Pauli operators
sx = qt.sigmax()
sy = qt.sigmay()
sz = qt.sigmaz()
# define Hamiltonian
H = Hamiltonian(sz, [(drv1, sx), (drv2, sy)])
Defining the quantum simulation problem¶
Now that we've defined our Hamiltonian, we can start setting up a quantum simulation problem. In QruiseML, we use the Problem class to group together the equations and parameters that govern the system. To instantiate the Problem, we need:
- the equation that governs the dynamics (e.g.
"Schroedinger"or"Master Equation") - the Hamiltonian (
H) - the initial qubit state (
u0) - the time interval of the simulation (
t0totfinal) - the pulse parameters (
ps)
Note: If you select "Master Equation", the absence or presence of collapse operators (c_ops) determines whether the von Neumann or Lindblad equation is used.
from qruise.toolset import Problem
t0 = 0.0 # start time
tfinal = 1.0 # end time
ts = np.linspace(t0, tfinal, 100) # to save the result at these timestamps
u0 = qt.basis(2, 0) # initial qubit state |0>
prob = Problem("Schroedinger", H, u0, (t0, tfinal), ps)
Starting a session and solving the problem¶
The Problem object defines the quantum system we want to simulate,
but it does not run the simulation itself. To do this, we need to create a Session and initialise the Problem object within it using qsprob_init. The session prepares the problem for numerical simulation and manages the solver execution.
from qruise.toolset.session import Session
sess = Session()
sess.qsprob_init(prob)
To compute the system time evolution, we use the evolve method to solve the quantum simulation problem numerically.
In the implementation below, "Tsit5" specifies the ODE algorithm, reltol
and abstol the numerical tolerances, and saveat the times at which the result is stored. Other solver options can also be passed as keyword arguments.
ds = sess.evolve("Tsit5", reltol=1e-3, abstol=1e-4, saveat=ts)
ds
ODE [ ] 0% ODE [########################################] 100%
<xarray.Dataset> Size: 4kB
Dimensions: (time: 100, row: 2)
Coordinates:
* time (time) float64 800B 0.0 0.0101 0.0202 0.0303 ... 0.9798 0.9899 1.0
Dimensions without coordinates: row
Data variables:
qstates (time, row) complex128 3kB (1+0j) ... (0.13077861932833706-0.479...Plotting the simulation results¶
The result returned by Session.evolve() is an xarray.Dataset object. It contains coordinates corresponding to the time axis and quantum-state labels (here, $|0\rangle$ and $|1\rangle$), as well as the variable qstates, which stores either the state vector or density matrix of the system.
We can also add derived quantities to the dataset. For instance, we can use it to compute the state populations and store them as a new variable. We can also rename the row dimension to state and assign the basis-state labels $|0\rangle$ and $|1\rangle$ for clearer indexing and visualisation.
ds["population"] = np.abs(ds["qstates"]) ** 2 # calculate the population
ds = ds.rename({"row": "state"}) # rename the dimension "row" to "states"
ds = ds.assign_coords(state=[r"|0>", r"|1>"]) # assign labels to the dimensions
ds
<xarray.Dataset> Size: 6kB
Dimensions: (time: 100, state: 2)
Coordinates:
* time (time) float64 800B 0.0 0.0101 0.0202 ... 0.9798 0.9899 1.0
* state (state) <U3 24B '|0>' '|1>'
Data variables:
qstates (time, state) complex128 3kB (1+0j) ... (0.13077861932833706-...
population (time, state) float64 2kB 1.0 0.0 1.0 ... 0.2461 0.753 0.247xarray provides different backends to conveniently plot the result. For example, we can use hvplot with the native matplotlib backend to plot the population of each state:
import hvplot.xarray
ds.hvplot.line(x="time", y="population", by="state", xlabel="time [arb. u.]")
Great, you just simulated your first quantum system in QruiseML! You can now explore the other notebooks for more advanced examples covering different quantum platforms and QruiseML features.