Skip to content

Problems

In QruiseML, "problems" are used to define the dynamics of a quantum system. Under the hood, each Problem represents an ordinary differential equation (ODE) over a specified time interval with well-defined initial conditions. From a user perspective, a Problem packages the time-evolution equation, Hamiltonian, initial state, parameters, and time span into a single object, making simulation and optimisation straightforward and consistent.

Once you've instantiated your Problem, you can define the ordinary differential equation algorithm you want to use to solve it. This makes it easy to experiment with different methods on the same quantum system.

Problem

Problem is the generic class for specifying a quantum simulation problem. To instantiate the Problem, we need:

prob = Problem(eq, H, u0, (t0, t1), ps)

To perform the simulation, you need to start a Session and choose an algorithm.

Defining qubit states

Qubit states can be defined either using state vectors or density matrices.

State vectors

State vectors represent pure states, such as

\[ |0\rangle = \begin{bmatrix}1 \\ 0\end{bmatrix} \qquad |1\rangle = \begin{bmatrix}0 \\ 1\end{bmatrix} \qquad |+\rangle = \tfrac{1}{\sqrt{2}} \begin{bmatrix}1 \\ 1\end{bmatrix}, \]

where \(|0\rangle\), \(|1\rangle\), and \(|+\rangle\) are the ground, first excited, and plus (superposition) states, respectively.

These can be defined using numpy like so:

import numpy as np

# Basis states
ground_state = np.array([1.0, 0.0])      # |0>
excited_state  = np.array([0.0, 1.0])    # |1>

# Superposition state
superpos_state = (ground_state + excited_state) / np.sqrt(2)    # |+> = (|0> + |1>)/√2

Alternatively, you can define the basis states using QuTiP:

import qutip as qt

# Basis states
ground_state = qt.basis(2, 0)       # |0>
excited_state = qt.basis(2, 1)      # |1>

Density matrices

Density matrices provide a more general description of quantum states. They can represent both pure states and mixed states, and are essential when dealing with open systems or noise, where the state is no longer perfectly pure. For example:

\[ \rho_{0} = |0\rangle\langle 0| = \begin{bmatrix}1 & 0 \\ 0 & 0\end{bmatrix} \\[1.0em] \rho_{+} = |+\rangle\langle +| = \tfrac{1}{2}\begin{bmatrix}1 & 1 \\ 1 & 1\end{bmatrix} \\[1.0em] \rho_{\text{mixed}} = \tfrac{1}{2}|0\rangle\langle 0| + \tfrac{1}{2}|1\rangle\langle 1| = \begin{bmatrix}0.5 & 0 \\ 0 & 0.5\end{bmatrix}. \]

These can be defined using numpy like so:

# Density matrices
rho_ground = np.outer(ground_state, ground_state.conj())        # |0><0|

rho_plus   = np.outer(superpos_state, superpos_state.conj())    # |+><+|

rho_mixed  = 0.5 * np.outer(ground_state, ground_state.conj()) \
           + 0.5 * np.outer(excited_state, excited_state.conj())  # 50/50 mixture

Or in QuTip like so:

# Density matrices
rho_ground = ground_state.proj()    # |0><0|
rho_plus   = superpos_state.proj()  # |+><+|
rho_mixed  = 0.5 * ground_state.proj() + 0.5 * excited_state.proj()  # 50/50 mixture

QOCProblem

QOCProblem is used for running quantum optimal control problems. Instantiating QOCProblem is almost identical to Problem; however, it requires two extra inputs:

  • the desired (target) state (ut), which (like u0) should be provided as either a state vector (1D array) or a density matrix (2D array)
  • a loss function quantifying how far the final state of the simulation is from the desired target state (loss)

These extra inputs allow QruiseML to adjust your control parameters to get as close as possible to the desired state.

opt_prob = QOCProblem(eq, H, u0, (t0, t1), ps, ut, loss)

Loss functions

The loss function quantifies how far the final system state is from the desired target state. During optimisation, the simulation defined in QOCProblem is repeatedly solved while the drive parameters are updated to minimise the loss function.

In QruiseML, the loss function is specified using one of the following state-overlap measures:

  • "svo": the state vector overlap
  • "rsvo": the real part of the state vector overlap

For a target state \(|\psi_t\rangle\) and final simulated state \(|\psi(t=t_\text{final})\rangle\), the state vector overlap is given by

\[ \mathcal{F} = |\langle \psi(t=t_\text{final}) | \psi_t \rangle|^2. \]

The corresponding "svo" loss function is

\[ \mathcal{L}_\mathrm{svo} = 1 - \mathcal{F}. \]

The "rsvo" loss function instead uses only the real part of the overlap.

EnsembleProblem

As the name suggests, EnsembleProblem defines an ensemble of problems, allowing you to run many simulations in parallel. Instead of specifying a single Hamiltonian, initial state, time span, or parameter space, you can provide a list of values for one or more of these quantities. QruiseML then constructs an ensemble of problems containing all possible combinations of the supplied values. This makes it easy to perform parameter sweeps or compare different configurations efficiently. For example, you might sweep a range of flux values to identify the sweet spot of your device.

EnsembleProblem works in much the same way as Problem:

ens_prob = EnsembleProblem(eq, H, u0, (t0, t1), ps)

The only difference is that you can pass lists for the initial state, parameters, or time span, e.g.

ps = {
    "a": 1.0,
    "sigma": 0.1*t1,
    "offset": list(np.linspace(-5, 5, 10)),
    "scale": list(np.linspace(0.8, 1.2, 11)),
}

EnsembleQOCProblem

To perform optimal control on an ensemble, simply use EnsembleQOCProblem as you would QOCProblem:

ens_opt_prob = EnsembleQOCProblem(eq, H, u0, (t0, t1), ps, ut, loss)

The difference is, again, that you can provide a list of initial states, Hamiltonians, parameter spaces, and/or time spans.