User Manual

The User Manual 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. To determine how well the system dynamics meet the desired behavior, we formulate an "objective" for each of those quantum states.

Most commonly, this is represented by instantiating an Objective which contains the initial state, the generator for that state's dynamics, and a target state. A time grid for the dynamics is part of ControlProblem as tlist. The objective is fulfilled when the control parameters are chosen such that the initial state evolves into the target state.

A control problem with a single such objective 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 objective. For example, finding the control parameters that implement a two-qubit quantum gate $Ô$ on a quantum computer naturally translates into four simultaneous objectives, 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 objectives 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 objective 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 objectives. This is why we emphasize the formulation of the control problem in terms of multiple simultaneous objectives.

Sometimes, some of the objectives may be more important than others. In this case, the Objective 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 Objective 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 states in the objectives. 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, either mandatory (like the $χ = ∂J_T/∂⟨ϕ|$ required for Krotov's method) or optional, like constraints on the control parameters. 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 (Hamiltonians, Liouvillians) of the Objectives in the ControlProblem.

The QuantumControl.Controls.getcontrols method extracts the controls from the objectives. Each control is typically time-dependent, e.g. a function $ϵ(t)$ or a vector of pulse values on a time grid. The default format for the dynamical generators is that of a "nested" tuple, e.g. (Ĥ₀, (Ĥ₁, ϵ₁), (Ĥ₂, ϵ₂)) where Ĥ₀, Ĥ₁ and Ĥ₂ are (sparse) matrices, and ϵ₁ and ϵ₂ are functions of time. The format corresponds to a time-dependent Hamiltonian $Ĥ₀ + ϵ₁(t) Ĥ₁ + ϵ₂(t) Ĥ₂$. For custom types describing a Hamiltonian or Liouvillian, the QuantumControl.Controls.getcontrols method must be defined to extract the controls.

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).

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 Objective, there is also a propagate_objective function that provides a more convenient interface, automatically using the initial state and the dynamical generator from the objective.

A very typical overall workflow is to set up the control problem, then propagate the objectives with the guess control to see how the system behaves, run the optimization, and then propagate the objectives again with the optimized controls, to verify the success of the optimization. For plugging in the optimized controls, propagate_objective has a controls_map argument.

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.