Overview

This page describes the API of the QuantumControl package by outlining the general procedure for defining and solving quantum control problems. See the API for a detailed reference.

Setting up control problems

Quantum control problems are described by instantiating ControlProblem. Remember that a quantum control problem aims to find control parameters in the dynamical generators (Hamiltonians, Liouvillians) of a quantum system to steer the dynamics of the system in some desired way. The dynamics of system are probed by one or more quantum states, each with its particular dynamical generator, which we encode as a Trajectory object.

To determine how well the system dynamics meet the desired behavior, a Trajectory can have additional properties that are taken into account in the optimization functional. Most commonly, this is represented by a target_state property of the Trajectory. The objective is fulfilled when the control parameters are chosen such that the initial state of each Trajectory evolves into the target state, on the time grid given as tlist in the ControlProblem.

A control problem with a single such trajectory already encodes the common state-to-state problem, e.g., to initialize a system into an entangled state, or to control a chemical reaction. However, there are many control problems that require simultaneously solving more than one trajectory. For example, finding the control parameters that implement a two-qubit quantum gate $Ô$ on a quantum computer naturally translates to four simultaneous trajectories, one for each two-qubit basis state: $|00⟩ → Ô |00⟩$, $|01⟩ → Ô |01⟩$, $|10⟩ → Ô |10⟩$, $|00⟩ → Ô |11⟩$. By virtue of the linearity of Hilbert space, finding a simultaneous solution to these four trajectories means the any state $|Ψ⟩$ will then evolve as $|Ψ⟩ → Ô |Ψ⟩$.

Some optimal control frameworks treat the optimization of quantum gates by numerically evolving the gate itself, $Û(t=0) = I → Ô(t=T)$. This is perfectly compatible with our framework: we can have a single trajectory for an initial "state" $Û$ with a target "state" $Ô$. However, this approach does not scale well numerically when the logical subspace of the two-qubit gate is embedded in a significantly larger physical Hilbert space: $Û$ is quadratically larger than $|Ψ⟩$. Moreover, the various methods implemented in the QuantumControl package are inherently parallel with respect to multiple trajectories. This is why we emphasize the formulation of the control problem in terms of multiple simultaneous trajectories.

Sometimes, some of the trajectories may be more important than others. In this case, the Trajectory can be instantiated with a weight attribute. There are also situations where the notion of a "target state" is not meaningful. Coming back to the example of two-qubit quantum gates, one may wish to maximize the entangling power of the quantum gate, without requiring a specific gate. We extract the information about the entangling power of the dynamical generator by tracking the time evolution of a set of states (the Bell basis, as it happens), but there is no meaningful notion of a "target state". In this example, an Trajectory may be instantiated without the target_state attribute, i.e., containing only the initial_state and the generator. These are the minimal required attributes for any optimization.

Mathematically, the control problem is solved by minimizing a functional that is calculated from the time-propagated trajectory states. By convention, this functional is passed as a keyword argument J_T when instantiating the ControlProblem. Standard functionals are defined in the QuantumControl.Functionals module. Depending on the control method, there can be additional options. See the documentation of the various methods implementing optimize for the options required or supported by the different solvers. All of these options can be passed as keyword arguments when instantiating the ControlProblem[1], or they can be passed later to optimize/@optimize_or_load.

Controls and control parameters

The controls that the QuantumControl package optimizes are implicit in the dynamical generator (hamiltonian, liouvillian) of the Trajectories in the ControlProblem.

The QuantumControl.Controls.get_controls method extracts the controls from the trajectories. Each control is typically time-dependent, e.g., a function $ϵ(t)$ or a vector of pulse values on a time grid. For each control, QuantumControl.Controls.discretize and QuantumControl.Controls.discretize_on_midpoints discretizes the control to an existing time grid. For controls that are implemented through some custom type, these methods must be defined to enable piecewise-constant time propagation or an optimization that assumes piecewise-constant control (most notably, Krotov's method). See QuantumControl.Interfaces.check_control for the full interface required of a control.

See also the section on Dynamical Generators in the QuantumPropagators documentation and QuantumControl.Interfaces.check_generator for details on how generators and controls relate.

Time propagation

The QuantumControl package uses (and includes) QuantumPropagators.jl as the numerical back-end for simulating the time evolution of all quantum states. The main high-level function provided from that package is propagate, which simulates the dynamics of a quantum state over an entire time grid. In the context of a ControlProblem consisting of one or more Trajectory, there is also a propagate_trajectory function that provides a more convenient interface, automatically using the initial state and the dynamical generator from the trajectory.

A very typical overall workflow is to set up the control problem, then propagate the trajectories with the guess control to see how the system behaves, run the optimization, and then propagate the trajectories again with the optimized controls, to verify the success of the optimization. For plugging in the optimized controls, QuantumControl.Controls.substitute can be used.

Optimization

The most direct way to solve a ControlProblem is with the optimize routine. It has a mandatory method argument that then delegates the optimization to the appropriate sub-package implementing that method.

However, if the optimization takes more than a few minutes to complete, you should use @optimize_or_load instead of just optimize. This routine runs the optimization and then writes the result to file. When called again, it will then simply load the result instead of rerunning the optimization. The @optimize_or_load also embeds some metadata in the output file, including (by default) the commit hash of the project repository containing the script that called @optimize_or_load and the filename of the script and line number where the call was made.

The output file written by @optimize_or_load can be read via the load_optimization function. This can recover both the optimization result and the metadata.

  • 1The solvers that ship with QuantumControl ignore options they do not know about. So when setting up a ControlProblem it is safe to pass a superset of options for different optimization methods.