Hierarchical diagonalization

For a large circuit with many degrees of freedoms, a possibly efficient way of obtaining low-lying eigenenergies and eigenstates is to partition the system into several subsystems, and to use the low-lying energy eigenstates of each subsystems as basis states to diagonalize the full system Hamiltonian. The schematic diagram below illustrates how hierarchical diagonalization is performed:

HD

For the example of the zero-pi qubit, the expression from sym_hamiltonian shows that \(\theta_2\) corresponds to the harmonic zeta mode of the zero-pi qubit. The remaining variables \(\theta_1\) and \(\theta_3\) form the primary qubit degrees of freedom and may be considered a “separate”, weakly coupled subsystem.

System hierarchy

This idea of a subsystem hierarchy is made explicit by grouping circuit variable indices in a nested list:

[14]:
system_hierarchy = [[1,3], [2]]

This nested list groups variables \(1\) and \(3\) into one subsystem, and makes variable \(2\) a separate subsystem.

List nesting extends to multiple layers, so that more complex hierarchies can be captured.
For example, a zero-pi qubit coupled to an oscillator (variable \(4\)) could be associated with the hierarchy [[[1,3], [2]], [4]].

For convenience, a default list of truncated Hilbert space dimensions is generated by truncation_template:

[15]:
scq.truncation_template(system_hierarchy)
[15]:
$\displaystyle \left[ 6, \ 6\right]$

This template obtained this way is meant to provide a list of the right shape. The entries specifying the truncation levels should, of course, be adjusted.

Enabling hierarchical diagonalization

To enable hierarchical diagonalization, the system hierarchy and truncation scheme info are handed over to configure:

[16]:
zero_pi.cutoff_n_1 = 15
zero_pi.cutoff_ext_2 = 50
zero_pi.cutoff_ext_3 = 100
zero_pi.configure(system_hierarchy=system_hierarchy, subsystem_trunc_dims=[150, 30])

Note that the truncated dimension (specified using subsystem_trunc_dims in configure) for each of the subsystems should be less than \(N-1\), where \(N\) is the Hilbert space dimension of the subsystem.

Once the hierarchy is set, subsystem Hamiltonians can be viewed via

[17]:
zero_pi.sym_hamiltonian(subsystem_index=0, float_round=4)  # show Hamiltonian for subsystem 0
[17]:
$\displaystyle \left(40.0 Q_{3}^{2} + 0.04 n_{1}^{2} + 0.0799 n_{1} n_{g1}\right) - \left(- 0.008 θ_{3}^{2} + EJ \cos{\left(θ_{1} - 1.0 θ_{3} \right)} + EJ \cos{\left(θ_{1} + θ_{3} - 1.0 (2πΦ_{1}) \right)}\right)$

Hamiltonian terms describing the coupling between two subsystems are displayed via

[18]:
zero_pi.sym_interaction((0,1))  # show coupling terms between subsystems 0 and 1
[18]:
$\displaystyle 0$

(For the symmetric zero-pi qubit, the zeta mode and the primary qubit degrees of freedom decouple.)

Each subsystem has access to circuit methods, like eigenvals. Here are the eigenenergies for the zeta mode:

[19]:
zero_pi.subsystems[1].eigenvals()
[19]:
array([0.01777276, 0.05203508, 0.08763912, 0.10359738, 0.16700863,
       0.16783645])

Hierarchical diagonalization allows us to increase variable-specific cutoffs without exploding the dimension of the joint Hilbert space.

Runtime information

  • Computation of eigenvalues and eigenvectors is deferred until unavoidable.

  • Subsystem eigenenergies and eigenstates are stored internally for re-use (until they must be replaced by new ones).

For instance, in hierarchical-diagonalization mode, calling for the <circuit>.hamiltonian() necessitates the computation of eigenvalues and eigenstates of individual subsystems. The first run will involve subsystem diagonalization and will be slower; calls subsequent to that will re-use data and be accordingly faster.