The algorithm#

Problem formulation#

Pymablock finds a series of the unitary transformation \(\mathcal{U}\) (we use calligraphic letters to denote series) that block-diagonalizes the Hamiltonian

(1)#\[\begin{split}\mathcal{H} = H_0 + \mathcal{H}',\quad H_0 = \begin{pmatrix} H_0^{AA} & 0\\ 0 & H_0^{BB} \end{pmatrix},\end{split}\]

with \(\mathcal{H}' = \mathcal{H}'_{D} + \mathcal{H}'_{O}\) containing an arbitrary number and orders of perturbations with block-diagonal and block-offdiagonal components, respectively. The series here may be multivariate, and they represent sums of the form

\[ \mathcal{A} = \sum_{n_1=0}^\infty \sum_{n_2=0}^\infty \cdots \sum_{n_k=0}^\infty \lambda_1^{n_1} \lambda_2^{n_2} \cdots \lambda_k^{n_k} A_{n_1, n_2, \ldots, n_k}, \]

where \(\lambda_i\) are the perturbation parameters and \(A_{n_1, n_2, \ldots, n_k}\) are linear operators.

The problem statement, therefore, is finding \(\mathcal{U}\) and \(\tilde{\mathcal{H}}\) such that:

(2)#\[\tilde{\mathcal{H}} = \mathcal{U}^\dagger \mathcal{H} \mathcal{U},\quad \tilde{\mathcal{H}}^{AB} = 0,\quad \mathcal{U}^\dagger \mathcal{U} = 1,\]

where series multiply according to the Cauchy product:

\[ \mathcal{C} = \mathcal{A}\mathcal{B} \Leftrightarrow C_\mathbf{n} = \sum_{\mathbf{m} + \mathbf{p} = \mathbf{n}} A_\mathbf{m} B_\mathbf{p}. \]

How to solve the problem?#

There are many ways to solve this problem that give identical expressions for \(\mathcal{U}\) and \(\tilde{\mathcal{H}}\). We are searching for a procedure that satisfies three additional constraints:

  • It has the same complexity scaling as a Cauchy product.

  • It does not require multiplications by \(H_0\). This is because in perturbation theory, \(n\)-th order corrections to \(\tilde{\mathcal{H}}\) carry \(n\) energy denominators \(1/(E_i - E_j)\) (see here). Therefore, any additional multiplications by \(H_0\) must cancel with additional energy denominators. Multiplying by \(H_0\) is therefore unnecessary work, and it gives longer intermediate expressions.

  • It requires only one Cauchy product by \(\mathcal{H}_D\), the block-diagonal part of \(\mathcal{H}\). By considering \(\mathcal{H}_{O}=0\), we observe that \(\mathcal{H}_D\) must at least enter \(\tilde{\mathcal{H}}\) as an added term, without any products. Moreover, because \(\mathcal{U}\) depends on the entire Hamiltonian, there must be at least one Cauchy product by \(\mathcal{H}'_D\). This lower bound turns out to be possible to achieve.

The goal of our algorithm is thus to be efficient and to produce compact results that do not require further simplifications.

Our solution#

To find \(\mathcal{U}\), let us separate it into an identity and \(\mathcal{U}' = \mathcal{W} + \mathcal{V}\):

(3)#\[\mathcal{U} = 1 + \mathcal{U}' = 1 + \mathcal{W} + \mathcal{V},\quad \mathcal{W}^\dagger = \mathcal{W},\quad \mathcal{V}^\dagger = -\mathcal{V}.\]

First, we use the unitarity condition \(\mathcal{U}^\dagger \mathcal{U} = 1\) by substituting \(\mathcal{U}'\) into it and obtain

(4)#\[\begin{split}\toggle{ \mathcal{W} = \texttip{\color{red}{\ldots}}{click to expand} = -\frac{1}{2} \mathcal{U}'^\dagger \mathcal{U}'. }{ \begin{align} \mathcal{W} &= \frac{1}{2}(\mathcal{U}'^\dagger + \mathcal{U}') \\ &= \frac{1}{2} \Big[(1 + \mathcal{U}'^\dagger)(1+\mathcal{U}') - 1 - \mathcal{U}'^\dagger \mathcal{U}' \Big] \\ &= -\frac{1}{2} \mathcal{U}'^\dagger \mathcal{U}'. \end{align} } \endtoggle\end{split}\]

Because \(\mathcal{U}'\) has no \(0\)-th order term, \((\mathcal{U}'^\dagger \mathcal{U}')_\mathbf{n}\) does not depend on the \(\mathbf{n}\)-th order of \(\mathcal{U}'\) nor \(\mathcal{W}\). More generally, a Cauchy product \(\mathcal{A}\mathcal{B}\) where \(\mathcal{A}\) and \(\mathcal{B}\) have no \(0\)-th order terms depends on \(\mathcal{A}_1, \ldots, \mathcal{A}_{n-1}\) and \(\mathcal{B}_1, \ldots, \mathcal{B}_{n-1}\). This allows us to use Cauchy products to define recurrence relations, which we apply throughout the algorithm. Therefore, we compute \(\mathcal{W}\) as a Cauchy product of \(\mathcal{U}'\) with itself. This recurrence relation is the first secret ingredient of Pymablock

To compute \(\mathcal{U}'\) we also need to find \(\mathcal{V}\), which is defined by the requirement \(\tilde{\mathcal{H}}^{AB} = 0\). Additionally, we constrain \(\mathcal{V}\) to be block off-diagonal: \(\mathcal{V}^{AA} = \mathcal{V}^{BB} = 0\), so that the resulting unitary transformation is equivalent to the Schrieffer-Wolff transformation. In turn, this means that \(\mathcal{W}\) is block-diagonal and that the norm of \(\mathcal{U}'\) is minimal.

To find \(\mathcal{V}\), we need to first look at the transformed Hamiltonian:

\[ \tilde{\mathcal{H}} = \mathcal{U}^\dagger \mathcal{H} \mathcal{U} = \mathcal{H}_D + \mathcal{U}'^\dagger \mathcal{H}_D + \mathcal{H}_D \mathcal{U}' + \mathcal{U}'^\dagger \mathcal{H}_D \mathcal{U}' + \mathcal{U}^\dagger\mathcal{H}'_{O}\mathcal{U}, \]

where we used \(\mathcal{U}=1+\mathcal{U}'\) and \(\mathcal{H} = \mathcal{H}_D + \mathcal{H}'_{O}\), since \(H_0\) is block-diagonal by definition.

Because we want to avoid unnecessary products by \(\mathcal{H}_D\), we need to get rid of the terms that contain it by replacing them with an alternative expression. Our strategy is to define an auxiliary operator \(\mathcal{X}\) that we can compute without ever multiplying by \(\mathcal{H}_D\). Like \(\mathcal{U}'\), \(\mathcal{X}\) needs to be defined via a recurrence relation, which we will find later. Because the expression above has \(\mathcal{H}_D\) multiplied by \(\mathcal{U}'\) by the left and by the right, we get rid of these terms by making sure that \(\mathcal{H}_D\) multiplies terms from one side only. To achieve this, we choose \(\mathcal{X}=\mathcal{Y}+\mathcal{Z}\) to be the commutator between \(\mathcal{U}'\) and \(\mathcal{H}_D\):

(5)#\[\mathcal{X} \equiv [\mathcal{U}', \mathcal{H}_D] = \mathcal{Y} + \mathcal{Z}, \quad \mathcal{Y} \equiv [\mathcal{V}, \mathcal{H}_D] = \mathcal{Y}^\dagger,\quad \mathcal{Z} \equiv [\mathcal{W}, \mathcal{H}_D] = -\mathcal{Z}^\dagger,\]

where \(\mathcal{Y}\) is therefore block off-diagonal and \(\mathcal{Z}\), block diagonal. We use \(\mathcal{H}_D \mathcal{U}' = \mathcal{U}' \mathcal{H}_D -\mathcal{X}\) to move \(\mathcal{H}_D\) through to the right and find

(6)#\[\begin{split}\toggle{ \tilde{\mathcal{H}} = \texttip{\color{red}{\ldots}}{click to expand} = \mathcal{H}_D - \mathcal{X} - \mathcal{U}'^\dagger \mathcal{X} + \mathcal{U}^\dagger\mathcal{H}'_{O}\mathcal{U}, }{ \begin{align*} \tilde{\mathcal{H}} &= \mathcal{H}_D + \mathcal{U}'^\dagger \mathcal{H}_D + (\mathcal{H}_D \mathcal{U}') + \mathcal{U}'^\dagger \mathcal{H}_D \mathcal{U}' + \mathcal{U}^\dagger\mathcal{H}'_{O}\mathcal{U} \\ &= \mathcal{H}_D + \mathcal{U}'^\dagger \mathcal{H}_D + \mathcal{U}'\mathcal{H}_D - \mathcal{X} + \mathcal{U}'^\dagger (\mathcal{U}' \mathcal{H}_D - \mathcal{X}) + \mathcal{U}^\dagger\mathcal{H}_{O}\mathcal{U}\\ &= \mathcal{H}_D + (\mathcal{U}'^\dagger + \mathcal{U}' + \mathcal{U}'^\dagger \mathcal{U}')\mathcal{H}_D - \mathcal{X} - \mathcal{U}'^\dagger \mathcal{X} + \mathcal{U}^\dagger\mathcal{H}'_{O}\mathcal{U}\\ &= \mathcal{H}_D - \mathcal{X} - \mathcal{U}'^\dagger \mathcal{X} + \mathcal{U}^\dagger\mathcal{H}'_{O}\mathcal{U}, \end{align*} } \endtoggle\end{split}\]

where the terms multiplied by \(\mathcal{H}_D\) cancel by unitarity.

The transformed Hamiltonian does not contain products by \(\mathcal{H}_D\) anymore, but it does depend on \(\mathcal{X}\), an auxiliary operator whose recurrent definition we do not know yet. To find it, we first focus on its anti-Hermitian part, \(\mathcal{Z}\). Since recurrence relations are expressions whose right hand side contains Cauchy products between series, we need to find a way to make a product appear. This is where the unitarity condition \(\mathcal{U}'^\dagger + \mathcal{U}' = -\mathcal{U}'^\dagger \mathcal{U}'\) comes in handy and gives:

(7)#\[\begin{split}\toggle{ \mathcal{Z} = \texttip{\color{red}{\ldots}}{click to expand} = \frac{1}{2}(-\mathcal{U}'^\dagger\mathcal{X} + \mathcal{X}^\dagger\mathcal{U}'). }{ \begin{align} \mathcal{Z} &= \frac{1}{2} (\mathcal{X} - \mathcal{X}^{\dagger}) \\ &= \frac{1}{2}\Big[ (\mathcal{U}' + \mathcal{U}'^{\dagger}) \mathcal{H}_D - \mathcal{H}_D (\mathcal{U}' + \mathcal{U}'^{\dagger}) \Big] \\ &= \frac{1}{2} \Big[ - \mathcal{U}'^{\dagger} (\mathcal{U}'\mathcal{H}_D - \mathcal{H}_D \mathcal{U}') + (\mathcal{U}'\mathcal{H}_D - \mathcal{H}_D \mathcal{U}')^{\dagger} \mathcal{U}' \Big] \\ &= \frac{1}{2} (-\mathcal{U}'^{\dagger} \mathcal{X} + \mathcal{X}^{\dagger} \mathcal{U}'). \end{align} } \endtoggle\end{split}\]

Similar to computing \(W_\mathbf{n}\), computing \(Z_\mathbf{n}\) requires lower orders of \(\mathcal{X}\) and \(\mathcal{U}'\), all blocks included. This is our second secret ingredient✨

Then, we compute the Hermitian part of \(\mathcal{X}\) by requiring that \(\tilde{\mathcal{H}}^{AB} = 0\) and find

(8)#\[\mathcal{X}^{AB} = (\mathcal{U}^\dagger \mathcal{H}'_{O} \mathcal{U} - \mathcal{U}'^\dagger \mathcal{X})^{AB}.\]

Once again, despite \(\mathcal{X}\) enters the right hand side, because all the terms lack 0-th order, this defines a recursive relation for \(\mathcal{X}^{AB}\), and therefore \(\mathcal{Y}\). This is our last secret ingredient✨

The final part is straightforward: the definition of \(\mathcal{Y}\) in (5) fixes \(\mathcal{V}\) as a solution of:

(9)#\[\mathcal{V}^{AB}H_0^{BB} - H_0^{AA} \mathcal{V}^{AB} = \mathcal{Y}^{AB} - [\mathcal{V}, \mathcal{H}'_D]^{AB},\]

a Sylvester’s equation, which we only need to solve once for every new order. This is the only step in the algorithm that requires a direct multiplication by \(\mathcal{H}'_D\). In the eigenbasis of \(H_0\), the solution of Sylvester’s equation is \(V^{AB}_{\mathbf{n}, ij} = (\mathcal{Y} - [\mathcal{V}, \mathcal{H}'_D])^{AB}_{\mathbf{n}, ij}/(E_i - E_j)\), where \(E_i\) are the eigenvalues of \(H_0\). However, even if the eigenbasis of \(H_0\) is not available, there are efficient algorithms to solve Sylvester’s equation, see below.

Actual algorithm#

We now have the complete algorithm:

  1. Define series \(\mathcal{U}'\) and \(\mathcal{X}\) and make use of their block structure and Hermiticity.

  2. To define the diagonal blocks of \(\mathcal{U}'\), use \(\mathcal{W} = -\mathcal{U}'^\dagger\mathcal{U}'/2\).

  3. To find the off-diagonal blocks of \(\mathcal{U}'\), solve Sylvester’s equation \(\mathcal{V}^{AB}H_0^{BB} - H_0^{AA}\mathcal{V}^{AB} = \mathcal{Y}^{AB} - [\mathcal{V}, \mathcal{H}'_D]\). This requires \(\mathcal{X}\).

  4. To find the diagonal blocks of \(\mathcal{X}\), define \(\mathcal{Z} = (-\mathcal{U}'^\dagger\mathcal{X} + \mathcal{X}^\dagger\mathcal{U}')/2\).

  5. For the off-diagonal blocks of \(\mathcal{X}\), use \(\mathcal{Y}^{AB} = (-\mathcal{U}'^\dagger\mathcal{X} + \mathcal{U}^\dagger\mathcal{H}'_{O}\mathcal{U})^{AB}\).

  6. Compute the effective Hamiltonian as \(\tilde{\mathcal{H}}_{D} = \mathcal{H}_D - \mathcal{X} - \mathcal{U}'^\dagger \mathcal{X} + \mathcal{U}^\dagger\mathcal{H}'_{O}\mathcal{U}\).

How to use Pymablock on large numerical Hamiltonians?#

Solving Sylvester’s equation and computing the matrix products are the most expensive steps of the algorithms for large Hamiltonians. Pymablock can efficiently construct an effective Hamiltonian of a small subspace even when the full Hamiltonian is a sparse matrix that is too costly to diagonalize. It does so by avoiding explicit computation of operators in \(B\) subspace, and by utilizing the sparsity of the Hamiltonian to compute the Green’s function. To do so, Pymablock uses either the MUMPS sparse solver via the python-mumps wrapper or the KPM method.

This approach was originally introduced in this work.