krotov.optimize module




Use Krotov’s method to optimize towards the given objectives.

__all__: optimize_pulses


krotov.optimize.optimize_pulses(objectives, pulse_options, tlist, *, propagator, chi_constructor, mu=None, sigma=None, iter_start=0, iter_stop=5000, check_convergence=None, info_hook=None, modify_params_after_iter=None, storage='array', parallel_map=None, store_all_pulses=False, continue_from=None, skip_initial_forward_propagation=False, norm=None, overlap=None)[source]

Use Krotov’s method to optimize towards the given objectives.

Optimize all time-dependent controls found in the Hamiltonians or Liouvillians of the given objectives.

  • objectives (list[Objective]) – List of objectives

  • pulse_options (dict) –

    Mapping of time-dependent controls found in the Hamiltonians of the objectives to a dictionary of options for that control. There must be options given for every control. As numpy arrays are unhashable and thus cannot be used as dict keys, the options for a control that is an array must be set using the key id(control) (see the example below). The options of any particular control must contain the following keys:

    • 'lambda_a': the Krotov step size (float value). This governs the overall magnitude of the pulse update. Large values result in small updates. Small values may lead to sharp spikes and numerical instability.

    • 'update_shape' : Function S(t) in the range [0, 1] that scales the pulse update for the pulse value at t. This can be used to ensure boundary conditions (S(0) = S(T) = 0), and enforce smooth switch-on and switch-off. This can be a callable that takes a single argument t; or the values 1 or 0 for a constant update-shape. The value 0 disables the optimization of that particular control.

    In addition, the following keys may occur:

    • 'args': If the control is a callable with arguments (t, args) (as required by QuTiP), a dict of argument values to pass as args. If 'args' is not specified via the pulse_options, controls will be discretized using the default args=None.

    For example, for objectives that contain a Hamiltonian of the form [H0, [H1, u], [H2, g]], where H0, H1, and H2 are Qobj instances, u is a numpy array

    >>> u = numpy.zeros(1000)

    and g is a control function

    >>> def g(t, args):
    ...     E0 = args.get('E0', 0.0)
    ...     return E0

    then a possible value for pulse_options would look like this:

    >>> from krotov.shapes import flattop
    >>> from functools import partial
    >>> pulse_options = {
    ...     id(u): {'lambda_a': 1.0, 'update_shape': 1},
    ...     g: dict(
    ...         lambda_a=1.0,
    ...         update_shape=partial(
    ...             flattop, t_start=0, t_stop=10, t_rise=1.5
    ...         ),
    ...         args=dict(E0=1.0)
    ...     )
    ... }

    The use of dict and the {...} syntax are completely equivalent, but dict is better for nested indentation.

  • tlist (numpy.ndarray) – Array of time grid values, cf. mesolve()

  • propagator (callable or list[callable]) – Function that propagates the state backward or forwards in time by a single time step, between two points in tlist. Alternatively, a list of functions, one for each objective. If the propagator is stateful, it should be an instance of krotov.propagators.Propagator. See krotov.propagators for details.

  • chi_constructor (callable) – Function that calculates the boundary condition for the backward propagation. This is where the final-time functional (indirectly) enters the optimization. See krotov.functionals for details.

  • mu (None or callable) – Function that calculates the derivative \(\frac{\partial H}{\partial\epsilon}\) for an equation of motion \(\dot{\phi}(t) = -i H[\phi(t)]\) of an abstract operator \(H\) and an abstract state \(\phi\). If None, defaults to, which covers the standard Schrödinger and master equations. See for a full explanation of the role of mu in the optimization, and the required function signature.

  • sigma (None or krotov.second_order.Sigma) – Function (instance of a Sigma subclass) that calculates the second-order contribution. If None, the first-order Krotov method is used.

  • iter_start (int) – The formal iteration number at which to start the optimization

  • iter_stop (int) – The iteration number after which to end the optimization, whether or not convergence has been reached

  • check_convergence (None or callable) – Function that determines whether the optimization has converged. If None, the optimization will only end when iter_stop is reached. See krotov.convergence for details.

  • info_hook (None or callable) – Function that is called after each iteration of the optimization, for the purpose of analysis. Any value returned by info_hook (e.g. an evaluated functional \(J_T\)) will be stored, for each iteration, in the info_vals attribute of the returned Result. The info_hook must have the same signature as krotov.info_hooks.print_debug_information(). It should not modify its arguments in any way, except for shared_data.

  • modify_params_after_iter (None or callable) – Function that is called after each iteration, which may modify its arguments for certain advanced use cases, such as dynamically adjusting lambda_vals, or applying spectral filters to the optimized_pulses. It has the same interface as info_hook but should not return anything. The modify_params_after_iter function is called immediately before info_hook, and can transfer arbitrary data to any subsequent info_hook via the shared_data argument.

  • storage (callable) – Storage constructor for the storage of propagated states. Must accept an integer parameter N and return an empty array-like container of length N. The default value ‘array’ is equivalent to functools.partial(numpy.empty, dtype=object).

  • parallel_map (callable or tuple or None) – Parallel function evaluator. If given as a callable, the argument must have the same specification as qutip.parallel.serial_map(). A value of None is the same as passing qutip.parallel.serial_map(). If given as a tuple, that tuple must contain three callables, each of which has the same specification as qutip.parallel.serial_map(). These three callables are used to parallelize (1) the initial forward-propagation, (2) the backward-propagation under the guess pulses, and (3) the forward-propagation by a single time step under the optimized pulses. See krotov.parallelization for details.

  • store_all_pulses (bool) – Whether or not to store the optimized pulses from all iterations in Result.

  • continue_from (None or Result) – If given, continue an optimization from a previous Result. The result must have identical objectives.

  • skip_initial_forward_propagation (bool) – If given as True together with continue_from, skip the initial forward propagation (“zeroth iteration”), and take the forward-propagated states from Result.states instead.

  • norm (callable or None) – A single-argument function to calculate the norm of states. If None, delegate to the norm() method of the states.

  • overlap (callable or None) – A two-argument function to calculate the complex overlap of two states. If None, delegate to qutip.Qobj.overlap() for Hilbert space states and to the Hilbert-Schmidt norm \(\tr[\rho_1^\dagger \rho2]\) for density matrices or operators.


The result of the optimization.

Return type



ValueError – If any controls are not real-valued, or if any update shape is not a real-valued function in the range [0, 1]; if using continue_from with a Result with differing objectives; if there are any required keys missing in pulse_options.