Esempio n. 1
0
def _get_integrals_second_order(d, E, eigval, dt, t0):
    # second order
    dE_bufs = (np.empty(
        (d, d, d, d), dtype=float), np.empty(
            (len(E), d, d), dtype=float), np.empty((len(E), d, d),
                                                   dtype=float))
    exp_buf = np.empty((len(E), d, d), dtype=complex)
    frc_bufs = (np.empty((len(E), d, d),
                         dtype=complex), np.empty((d, d, d, d), dtype=complex))
    int_buf = np.empty((len(E), d, d, d, d), dtype=complex)
    msk_bufs = np.empty((2, len(E), d, d, d, d), dtype=bool)
    tspace = np.linspace(0, dt, 1001) + t0
    dE = np.subtract.outer(eigval, eigval)

    ex = (np.multiply.outer(dE, tspace - t0) +
          np.multiply.outer(E, tspace)[:, None, None])
    I1 = integrate.cumtrapz(util.cexp(ex), tspace, initial=0)
    ex = (np.multiply.outer(dE, tspace - t0) -
          np.multiply.outer(E, tspace)[:, None, None])
    integrand = util.cexp(ex)[:, :, :, None, None] * I1[:, None, None]

    integral_numeric = integrate.trapz(integrand, tspace)
    integral = numeric._second_order_integral(E, eigval, dt, int_buf, frc_bufs,
                                              dE_bufs, exp_buf, msk_bufs)
    return integral, integral_numeric
Esempio n. 2
0
    def test_cexp(self):
        """Fast complex exponential."""
        x = rng.standard_normal((50, 100))
        a = util.cexp(x)
        b = np.exp(1j * x)
        self.assertArrayAlmostEqual(a, b)

        a = util.cexp(-x)
        b = np.exp(-1j * x)
        self.assertArrayAlmostEqual(a, b)
Esempio n. 3
0
def liouville_derivative(dt: Coefficients, propagators: ndarray, basis: Basis,
                         eigvecs: ndarray, eigvals: ndarray,
                         transf_control_operators: ndarray) -> ndarray:
    r"""
    Calculate the derivatives of the control propagators in Liouville
    representation.

    Parameters
    ----------
    dt : array_like, shape (n_dt)
        Sequence duration, i.e. for the :math:`g`-th pulse
        :math:`t_g - t_{g-1}`.
    propagators : array_like, shape (n_dt+1, d, d)
        The propagators :math:`Q_g = P_g P_{g-1}\cdots P_0` as a (d, d) array
        with *d* the dimension of the Hilbert space.
    basis : Basis, shape (d**2, d, d)
        The basis elements, in which the pulse control matrix will be expanded.
    eigvecs : array_like, shape (n_dt, d, d)
        Eigenvector matrices for each time pulse segment *g* with the first
        axis counting the pulse segment, i.e.
        ``HV == array([V_0, V_1, ...])``.
    eigvals : array_like, shape (n_dt, d)
        Eigenvalue vectors for each time pulse segment *g* with the first
        axis counting the pulse segment, i.e.
        ``HD == array([D_0, D_1, ...])``.
    transf_control_operators : array_like, shape (n_dt, c_ctrl, d, d)
        The control operators transformed into the eigenspace of the control
        Hamiltonian. The drift operators are ignored, if identifiers for
        accessible control operators are provided.

    Returns
    -------
    dL: array_like, shape (n_dt, n_ctrl, n_dt, d**2, d**2)
        The derivative of the control propagators in Liouville representation
        :math:`\frac{\partial \mathcal{Q}_{jk}^{(g)}}
        {\partial u_h(t_{g^\prime})}`.
        The array's indexing has shape :math:`(g,h,g^\prime,j,k)`.

    Notes
    -----
    The derivatives of the control propagators in Liouville representation are
    calculated according to

    .. math::

        \frac{\partial\mathcal{Q}_{jk}^{(g-1)}}{\partial u_h(t_{g^\prime})} &=
        \Theta_{g-1}(g^\prime) \mathrm{tr}\Big(
            \frac{\partial U_c^\dagger(t_{g-1},0)}{\partial u_h(t_{g^\prime})}
            C_j U_c(t_{g-1},0) C_k\Big)\\
            &+\Theta_{g-1}(g^\prime)\mathrm{tr}\Big(U_c^\dagger(t_{g-1},0)C_j
                                                \frac{\partial U_c(t_{g-1},0)}
                                                {\partial u_h(t_{g^\prime})}C_k
                                                \Big),

    where :math:`\Theta_{g-1}(g^\prime)` being 1 if :math:`g^\prime < g-1` and
    zero otherwise.

    """
    n = len(dt)
    d = basis.shape[-1]

    # Allocate memory
    A_mat = np.empty((d, d), dtype=complex)
    partial_U = np.empty((n, transf_control_operators.shape[1], d, d),
                         dtype=complex)
    deriv_U = np.zeros((n, n, transf_control_operators.shape[1], d, d),
                       dtype=complex)

    for g in (range(n)):
        omega_diff = np.subtract.outer(eigvals[g], eigvals[g])
        mask = (abs(omega_diff) < 1e-10)
        A_mat[mask] = dt[g]  # if the integral diverges
        A_mat[~mask] = (cexp(omega_diff[~mask]*dt[g]) - 1) \
            / (1j * omega_diff[~mask])
        # Calculate dU(t_g,t_{g-1})/du_h within one time step
        partial_U[g] = -1j * np.einsum(
            'ia,ja,jk,hkl,kl,ml->him',
            propagators[g + 1],
            propagators[g].conj(),
            eigvecs[g],
            transf_control_operators[g],
            A_mat,
            eigvecs[g].conj(),
            optimize=['einsum_path', (3, 4), (0, 1), (0, 3), (0, 1), (0, 1)])
        # Calculate the whole propagator derivative
        for g_prime in range(g + 1):  # condition g' <= g-1
            deriv_U[g, g_prime] = np.einsum(
                'ij,kj,hkl,lm->him',
                propagators[g + 1],
                propagators[g_prime + 1].conj(),
                partial_U[g_prime],
                propagators[g_prime],
                optimize=['einsum_path', (0, 1), (0, 1), (0, 1)])

    # Now calculate derivative of Liouville representation
    # Calculate traces first to save memory
    sum1 = np.einsum('tshba,jbc,tcd,kda->thsjk',
                     deriv_U.conj(),
                     basis,
                     propagators[1:],
                     basis,
                     optimize=['einsum_path', (1, 2), (0, 2), (0, 1)])
    sum2 = np.einsum('tba,jbc,tshcd,kda->thsjk',
                     propagators[1:].conj(),
                     basis,
                     deriv_U,
                     basis,
                     optimize=['einsum_path', (0, 1), (0, 2), (0, 1)])
    liouville_deriv = sum1 + sum2

    return liouville_deriv
Esempio n. 4
0
def calculate_derivative_of_control_matrix_from_scratch(
        omega: Coefficients,
        propagators: ndarray,
        Q_Liou: ndarray,
        eigvals: ndarray,
        eigvecs: ndarray,
        basis: Basis,
        t: Coefficients,
        dt: Coefficients,
        n_opers: Sequence[Operator],
        n_coeffs: Sequence[Coefficients],
        c_opers: Sequence[Operator],
        all_identifiers: Sequence[str],
        control_identifiers: Optional[Sequence[str]] = None,
        s_derivs: Optional[Sequence[Coefficients]] = None) -> ndarray:
    r"""
    Calculate the derivative of the control matrix from scratch.

    Parameters
    ----------
    omega : array_like, shape (n_omega,)
        Frequencies, at which the pulse control matrix is to be evaluated.
    propagators : array_like, shape (n_dt+1, d, d)
        The propagators :math:`Q_g = P_g P_{g-1}\cdots P_0` as a (d, d) array
        with *d* the dimension of the Hilbert space.
    Q_Liou : ndarray, shape (n_dt+1, d**2, d**2)
        The Liouville representation of the cumulative control propagators
        U_c(t_g,0).
    eigvals : array_like, shape (n_dt, d)
        Eigenvalue vectors for each time pulse segment *g* with the first
        axis counting the pulse segment, i.e.
        ``HD == array([D_0, D_1, ...])``.
    eigvecs : array_like, shape (n_dt, d, d)
        Eigenvector matrices for each time pulse segment *g* with the first
        axis counting the pulse segment, i.e.
        ``HV == array([V_0, V_1, ...])``.
    basis : Basis, shape (d**2, d, d)
        The basis elements, in which the pulse control matrix will be expanded.
    t : array_like, shape (n_dt+1), optional
        The absolute times of the different segments.
    dt : array_like, shape (n_dt)
        Sequence duration, i.e. for the :math:`g`-th pulse
        :math:`t_g - t_{g-1}`.
    n_opers : array_like, shape (n_nops, d, d)
        Noise operators :math:`B_\alpha`.
    n_coeffs : array_like, shape (n_nops, n_dt)
        The sensitivities of the system to the noise operators given by
        *n_opers* at the given time step.
    c_opers : array_like, shape (n_cops, d, d)
        Control operators :math:`H_k`.
    all_identifiers : array_like, shape (n_cops)
        Identifiers of all control operators.
    control_identifiers : Sequence[str], shape (n_ctrl), Optional
        Sequence of strings with the control identifiers to distinguish between
        accessible control and drift Hamiltonian. The default is None.
    s_derivs : array_like, shape (n_nops, n_ctrl, n_dt)
        The derivatives of the noise susceptibilities by the control amplitudes.
        Defaults to None.

    Raises
    ------
    ValueError
        If the given identifiers *control_identifier* are not subset of the
        total identifiers *all_identifiers* of all control operators.

    Returns
    -------
    dR : array_like, shape (n_nops, d**2, n_dt, n_ctrl, n_omega)
        Partial derivatives of the total control matrix with respect to each
        control direction
        :math:`\frac{\partial R_{\alpha k}(\omega)}{\partial u_h(t_{g'})}`.
        The array's indexing has shape :math:`(\alpha,k,g^\prime,h,\omega)`.

    Notes
    -----
    The derivative of the control matrix is calculated according to

    .. math ::

        \frac{\partial R_{\alpha k}(\omega)}{\partial u_h(t_{g'})} =
            \sum_{g=1}^G \mathrm{e}^{\mathrm{i}\omega t_{g-1}}\cdot\left(\sum_j
                \left[\frac{\partial R_{\alpha j}^{(g)}(\omega)}
                    {\partial u_h(t_{g'})} \cdot \mathcal{Q}_{jk}^{(g-1)}
                +   R_{\alpha j}^{(g)}(\omega)
                \cdot\frac{\partial \mathcal{Q}_{jk}^{(g-1)}}
                {\partial u_h(t_{g'})} \right] \right)

    See Also
    --------
    :func:`liouville_derivative`
    :func:`control_matrix_at_timestep_derivative`
    """
    # Distinction between control and drift operators and only calculate the
    # derivatives in control direction

    path = ['einsum_path', (0, 1), (0, 1)]
    if (control_identifiers is None):
        VHV = np.einsum('tji,hjk,tkl->thil',
                        eigvecs.conj(),
                        c_opers,
                        eigvecs,
                        optimize=path)
    elif (set(control_identifiers) <= set(all_identifiers)):
        dict_id_oper = dict(zip(all_identifiers, c_opers))
        control = [dict_id_oper[element] for element in control_identifiers]
        VHV = np.einsum('tji,hjk,tkl->thil',
                        eigvecs.conj(),
                        control,
                        eigvecs,
                        optimize=path)
    else:
        raise ValueError('Given control identifiers have to be a \
                         subset of (drift+control) Hamiltonian!')
    # Get derivative of Liouville, control matrix at each time step, derivative
    # derivative of control matrix at each time step
    dL = liouville_derivative(dt=dt,
                              propagators=propagators,
                              basis=basis,
                              eigvecs=eigvecs,
                              eigvals=eigvals,
                              transf_control_operators=VHV)
    ctrlmat_data = control_matrix_at_timestep_derivative(
        omega=omega,
        dt=dt,
        eigvals=eigvals,
        eigvecs=eigvecs,
        basis=basis,
        n_opers=n_opers,
        n_coeffs=n_coeffs,
        transf_control_operators=VHV,
        s_derivs=s_derivs)
    ctrlmat_g = ctrlmat_data['R_g']
    ctrlmat_g_deriv = ctrlmat_data['dR_g']

    # Calculate the derivative of the total control matrix
    exp_factor = cexp(np.multiply.outer(t, omega))
    summand1 = np.einsum('to,tajho,tjk->aktho',
                         exp_factor[:-1],
                         ctrlmat_g_deriv,
                         Q_Liou[:-1],
                         optimize=['einsum_path', (1, 2), (0, 1)])
    summand2 = np.einsum('to,tajo,thsjk->aksho',
                         exp_factor[1:-1],
                         ctrlmat_g[1:],
                         dL[:-1],
                         optimize=['einsum_path', (0, 1), (0, 1)])

    dR = summand1 + summand2
    return dR
Esempio n. 5
0
def control_matrix_at_timestep_derivative(
        omega: Coefficients,
        dt: Coefficients,
        eigvals: ndarray,
        eigvecs: ndarray,
        basis: Basis,
        n_opers: Sequence[Operator],
        n_coeffs: Sequence[Coefficients],
        transf_control_operators: ndarray,
        s_derivs: Optional[Sequence[Coefficients]] = None) -> dict:
    r"""
    Calculate the control matrices and corresponding derivatives.

    Calculate control matrices at each time step and the corresponding partial
    derivatives of those with respect to control strength at each time step.

    Parameters
    ----------
    omega : array_like, shape (n_omega)
        Frequencies, at which the pulse control matrix is to be evaluated.
    dt : array_like, shape (n_dt)
        Sequence duration, i.e. for the :math:`g`-th pulse
        :math:`t_g - t_{g-1}`.
    eigvals : array_like, shape (n_dt, d)
        Eigenvalue vectors for each time pulse segment *g* with the first
        axis counting the pulse segment, i.e.
        ``HD == array([D_0, D_1, ...])``.
    eigvecs : array_like, shape (n_dt, d, d)
        Eigenvector matrices for each time pulse segment *g* with the first
        axis counting the pulse segment, i.e.
        ``HV == array([V_0, V_1, ...])``.
    basis : Basis, shape (d**2, d, d)
        The basis elements, in which the pulse control matrix will be expanded.
    n_opers : array_like, shape (n_nops, d, d)
        Noise operators :math:`B_\alpha`.
    n_coeffs : array_like, shape (n_nops, n_dt)
        The sensitivities of the system to the noise operators given by
        *n_opers* at the given time step.
    transf_control_operators : array_like, shape (n_dt, n_ctrl, d, d)
        The control operators transformed into the eigenspace of the control
        Hamiltonian. The drift operators are ignored, if identifiers for
        accessible control operators are provided.
    s_derivs : array_like, shape (n_nops, n_ctrl, n_dt)
        The derivatives of the noise susceptibilities by the control amplitudes.
        Defaults to None.

    Returns
    -------
    ctrlmat_data : dict {'R_g': R_g, 'dR_g': gradient}
        * **R_g** *(array_like, shape (n_dt, n_nops, d**2, n_omega))*
        The control matrix at each time step
        :math:`\mathcal{R}_{\alpha j}^{(g)}(\omega)` is identified with R_g.
        The array's indexing has shape :math:`(g,\alpha,j,\omega)`.

        * **dR_g** *(array_like, shape (n_dt, n_nops, d**2, n_ctrl, n_omega))*
        The corresponding derivative with respect to the control strength
        :math:`\frac{\partial\mathcal{R}_{\alpha j}^{(g)}(\omega)}
        {\partial u_h(t_{g^\prime})}` is identified with dR_g. The array's
        indexing has shape :math:`(g^\prime,\alpha,j,h,\omega)`. Here, only one
        time axis is needed, since the derivative is zero for
        :math:`g\neq g^\prime`.


    Notes
    -----
    The control matrix at each time step is evaluated according to

    .. math::

            \mathcal{R}_{\alpha j}^{(g)}(\omega) = s_\alpha^{(g)}\mathrm{tr}
            \left([\bar{B}_\alpha \circ I_1^{(g)}(\omega)] \bar{C}_j \right),

    where

    .. math::

        I_{1,nm}^{(g)}(\omega) = \frac{\exp(\mathrm{i}(\omega + \omega_n^{(g)}
                                            - \omega_m^{(g)}) \Delta t_g) - 1}
        {\mathrm{i}(\omega + \omega_n^{(g)} - \omega_m^{(g)})}

    The derivative of the control matrix with respect to the control strength
    at different time steps is calculated according to

    .. math::

        \frac{\partial \mathcal{R}_{\alpha j}^{(g)}(\omega)}
        {\partial u_h(t_{g^\prime})} =
        \mathrm{i}\delta_{gg^\prime} s_\alpha^{(g)} \mathrm{tr}
        \left( \bar{B}_{\alpha} \cdot \mathbb{M} \right)
        + \frac{\partial s_\alpha^{(g)}}{u_h (t_{g^\prime})} \text{tr}
        \left( (\overline{B}_{\alpha} \circ I_1^{(g)}{}(\omega)) \cdot
        \overline{C}_{j}) \right).

    We assume that the noise susceptibility :math:`s` only depends locally
    on the time i.e. :math:`\partial_{u(t_g)} s(t_{g^\prime})
    = \delta_{gg^\prime} \partial_{u(t_g)} s(t_g)`
    If denoting :math:`\Delta\omega_{ij} = \omega_i^{(g)} - \omega_j^{(g)}`
    the integral part is encapsulated in

    .. math::

        \mathbb{M}_{mn} = \left[ \bar{C}_j, \mathbb{I}^{(mn)}
                                \circ \bar{H}_h \right]_{mn},

    with

    .. math::

        \mathbb{I}_{pq}^{(mn)} &= \delta_{pq} \left(
            \frac{\Delta t_g \cdot
                  \exp(\mathrm{i}(\omega + \Delta\omega_{nm})\Delta t_g)}
            {\mathrm{i}(\omega + \Delta\omega_{nm})}
            + \frac{\exp(\mathrm{i}(\omega + \Delta\omega_{nm})\Delta t_g) - 1}
            {(\omega + \Delta\omega_{nm})^2}\right)\\
            &+  \frac{1-\delta_{pq}}{\mathrm{i}\Delta\omega_{pq}} \cdot
            \frac{\exp(\mathrm{i}(\omega + \Delta\omega_{nm}
                                  + \Delta\omega_{pq})\Delta t_g) - 1}
            {\mathrm{i}(\omega + \Delta\omega_{nm} + \Delta\omega_{pq})}\\
            &- \frac{1-\delta_{pq}}{\mathrm{i}\Delta\omega_{pq}} \cdot
            \frac{\exp(\mathrm{i}(\omega + \Delta\omega_{nm})\Delta t_g) - 1}
            {\mathrm{i}(\omega + \Delta\omega_{nm})}
    """
    d = eigvecs.shape[-1]
    n_dt = len(dt)
    E = omega

    # Precompute some transformation into eigenspace of control Hamiltonian
    path = ['einsum_path', (0, 1), (0, 1)]
    VBV = np.einsum('gji,ajk,gkl->gail',
                    eigvecs.conj(),
                    n_opers,
                    eigvecs,
                    optimize=path)
    VCV = np.einsum('tnm,jnk,tkl->tjml',
                    eigvecs.conj(),
                    basis,
                    eigvecs,
                    optimize=path)

    # Allocate memory
    R_g = np.empty((n_dt, len(n_opers), len(basis), len(E)), dtype=complex)
    R_g_d_s = np.empty(
        (n_dt, len(n_opers), len(basis), len(
            transf_control_operators[0]), len(E)),
        dtype=complex)
    # For calculating dR_g: d,d = p,q, d,d = m,n
    integral_deriv = np.empty((n_dt, len(E), d, d, d, d), dtype=complex)

    for g in range(n_dt):
        # Any combination of \omega_m-\omega_n (R_g), \omega_p-\omega_q (dR_g)
        dE = np.subtract.outer(eigvals[g], eigvals[g])
        # Any combination of \omega+\omega_m-\omega_n (R_g) or
        # \omega-\omega_m+\omega_n (dR_g)
        EdE = np.add.outer(E, dE)

        # 1) Calculation of the control matrix R_g at each time step
        integral_Rg = np.empty((len(E), d, d), dtype=complex)
        # Mask the integral to avoid convergence problems
        mask_Rg = np.abs(EdE * dt[g]) <= 1e-7
        integral_Rg[mask_Rg] = dt[g]
        integral_Rg[~mask_Rg] = (cexp(EdE[~mask_Rg]*dt[g]) - 1) \
            / (1j*(EdE[~mask_Rg]))

        R_g[g] = np.einsum('a,bcd,adc,hdc->abh',
                           n_coeffs[:, g],
                           VCV[g],
                           VBV[g],
                           integral_Rg,
                           optimize=['einsum_path', (0, 2), (0, 2), (0, 1)])

        # Add the dependency of the susceptibilities
        # s_derivs has shape (n_nops, n_ctrl, n_dt)
        # VCV has shape (n_dt, d**2, d, d)
        # VBV has shape (n_dt, n_nops, d, d)
        # integral_Rg has shape (n_omega, d, d)
        # R_g_d_s shall be of shape (n_dt, n_nops, d**2, n_ctrl, n_omega)
        if s_derivs is not None:
            R_g_d_s[g] = np.einsum(
                'ae,bcd,adc,hdc->abeh',
                s_derivs[:, :, g],
                VCV[g],
                VBV[g],
                integral_Rg,
                optimize=['einsum_path', (0, 2), (0, 2), (0, 1)])

        # 2) Calculation of derivatives of control matrices at each time step
        mask_deriv = (abs(dE) < 1e-15)  # case: \omega_p-\omega_q = 0
        # calculation if omega_diff = 0
        n_case = sum(sum(mask_deriv))
        a = dt[g]*cexp(EdE*dt[g]) / (1j * EdE) \
            + (cexp(EdE*dt[g]) - 1) / (EdE)**2
        integral_deriv[g, :, mask_deriv] = np.concatenate(([[a] * n_case]),
                                                          axis=0)
        # calculation if omega_diff != 0
        b1 = - (cexp(np.add.outer(EdE, dE[~mask_deriv])*dt[g]) - 1) \
            / (np.add.outer(EdE, dE[~mask_deriv])) / dE[~mask_deriv]
        b2 = +np.divide.outer(((cexp(EdE * dt[g]) - 1) / EdE), dE[~mask_deriv])
        integral_deriv[g, :, ~mask_deriv] = (b1 + b2).transpose(3, 0, 1, 2)

    # Computation of the derivative/ gradient
    I_circ_H = np.einsum('toijnm,thij->tohijnm', integral_deriv,
                         transf_control_operators)
    M_mat = (np.einsum('tjmk,tohknnm->tojhmn', VCV, I_circ_H) -
             np.einsum('tohmknm,tjkn->tojhmn', I_circ_H, VCV))
    gradient = 1j * np.einsum('at,tamn,tojhnm->tajho',
                              n_coeffs,
                              VBV,
                              M_mat,
                              optimize=['einsum_path', (1, 2), (0, 1)])
    if s_derivs is not None:
        gradient += R_g_d_s
    ctrlmat_data = {'R_g': R_g, 'dR_g': gradient}
    return ctrlmat_data