Loading [MathJax]/jax/output/HTML-CSS/jax.js

qml.labs.dla.variational_kak_adj

variational_kak_adj(H, g, dims, adj, verbose=False, opt_kwargs=None, pick_min=False)[source]

Variational KaK decomposition of Hermitian H using the adjoint representation.

Given a Cartan decomposition (cartan_decomp()) g=km, a Hermitian operator Hm, and a horizontal Cartan subalgebra (horizontal_cartan_subalgebra()) am, this function computes aa and Kceik such that

H=KcaKc.

In particular, a=jcjaj is decomposed in terms of commuting operators aja. This allows for the immediate decomposition

eitH=KceitaKc=Kc(jeitcjaj)Kc.

The result is provided in terms of the adjoint vector representation of aa (see adjvec_to_op()), i.e. the ordered coefficients cj in a=jcjmj with the basis elements mj(˜ma) and the optimal parameters θ such that

Kc=1j=|k|eiθjkj

for the ordered basis of k given by the first |k| elements of g. Note that we define Kc mathematically with the descending order of basis elements kjk such that the resulting circuit has the canonical ascending order. In particular, a PennyLane quantum function that describes the circuit given the optimal parameters theta_opt and the basis k containing the operators, is given by the following.

def Kc(theta_opt: Iterable[float], k: Iterable[Operator]):
    assert len(theta_opt) == len(k)
    for theta_j, k_j in zip(theta_opt, k):
        qml.exp(-1j * theta_j * k_j)

Internally, this function performs a modified version of 2104.00728, in particular minimizing the cost function

f(θ)=H,K(θ)ei|a|j=1πjajK(θ),

see eq. (6) therein and our demo for more details. Instead of relying on having Pauli words, we use the adjoint representation for a more general evaluation of the cost function. The rest is the same.

Parameters:
  • H (Union[Operator, PauliSentence, np.ndarray]) – Hamiltonian to decompose

  • g (List[Union[Operator, PauliSentence, np.ndarray]]) – DLA of the Hamiltonian

  • dims (Tuple[int]) – Tuple of dimensions (dim_k, dim_mtilde, dim_a) of Cartan decomposition g=k(˜ma)

  • adj (np.ndarray) – Adjoint representation of dimension (dim_g, dim_g, dim_g), with the implicit ordering (k, mtilde, a).

  • verbose (bool) – Plot the optimization. Requires matplotlib to be installed (pip install matplotlib)

  • opt_kwargs (dict) – Keyword arguments for the optimization like initial starting values for θ of dimension (dim_k,), given as theta0. Also includes n_epochs, lr, b1, b2, verbose, interrupt_tol, see run_opt()

  • pick_min (bool) – Whether to pick the parameter set with lowest cost function value during the optimization as optimal parameters. Otherwise picks the last parameter set.

Returns:

(adjvec_a, theta_opt): The adjoint vector representation adjvec_a of dimension (dim_mtilde + dim_a,), with respect to the basis of m=˜m+a of the CSA element aa s.t. H=KaK. For a successful optimization, the entries corresponding to ˜m should be close to zero. The second return value, theta_opt, are the optimal coefficients θ of the decomposition K=1j=|k|eiθjkj for the basis kjk.

Return type:

Tuple(np.ndarray, np.ndarray)

Example

Let us perform a KaK decomposition for the transverse field Ising model Hamiltonian, exemplarily for n=3 qubits on a chain. We start with some boilerplate code to perform a Cartan decomposition using the concurrence_involution(), which places the Hamiltonian in the horizontal subspace m. From this we re-order g=k+m and finally compute a horizontal_cartan_subalgebra() a in m=˜ma.

import pennylane as qml
import numpy as np
import jax.numpy as jnp
import jax

from pennylane import X, Z
from pennylane.liealg import (
    cartan_decomp,
    horizontal_cartan_subalgebra,
    check_cartan_decomp,
    concurrence_involution,
    adjvec_to_op,
)
from pennylane.labs.dla import (
    validate_kak,
    variational_kak_adj,
)

n = 3

gens = [X(i) @ X(i + 1) for i in range(n - 1)]
gens += [Z(i) for i in range(n)]
H = qml.sum(*gens)

g = qml.lie_closure(gens)
g = [op.pauli_rep for op in g]

involution = concurrence_involution

assert not involution(H)
k, m = cartan_decomp(g, involution=involution)
assert check_cartan_decomp(k, m)

g = k + m
adj = qml.structure_constants(g)

g, k, mtilde, a, adj = horizontal_cartan_subalgebra(g, k, m, adj, tol=1e-14, start_idx=0)

Due to the canonical ordering of all constituents, it suffices to tell variational_kak_adj the dimensions of dims = (len(k), len(mtilde), len(a)), alongside the Hamiltonian H, the Lie algebra g and its adjoint representation adj. Internally, the function is performing a variational optimization to find a local extremum of a suitably constructed loss function that finds as its extremum the decomposition

Kc=|k|j=1eiθjkj

in form of the optimal parameters {θj} for the respective kjk. The resulting K then informs the CSA element a of the KaK decomposition via a=KcHKc. This is detailed in 2104.00728.

>>> dims = (len(k), len(mtilde), len(a))
>>> adjvec_a, theta_opt = variational_kak_adj(H, g, dims, adj, opt_kwargs={"n_epochs": 3000})

As a result, we are provided with the adjoint vector representation of the CSA element aa with respect to the basis mtilde+a and the optimal parameters of dimension |k|

Let us perform some sanity checks to better understand the resulting outputs. We can turn that element back to an operator using adjvec_to_op() and from that to a matrix for which we can check Hermiticity.

m = mtilde + a
[a_op] = adjvec_to_op([adjvec_a], m)
a_m = qml.matrix(a_op, wire_order=range(n))
assert np.allclose(a_m, a_m.conj().T)

Let us now confirm that we get back the original Hamiltonian from the resulting Kc and a. In particular, we want to confirm H=KcaKc for Kc=|k|j=1eiθjkj.

assert len(theta_opt) == len(k)

def Kc(theta_opt):
    for th, op in zip(theta_opt, k):
        qml.exp(-1j * th * op.operation())

Kc_m = qml.matrix(Kc, wire_order=range(n))(theta_opt)

# check Unitary property of Kc
assert np.allclose(Kc_m.conj().T @ Kc_m, np.eye(2**n))

H_reconstructed = Kc_m @ a_m @ Kc_m.conj().T

H_m = qml.matrix(H, wire_order=range(len(H.wires)))

# check Hermitian property of reconstructed Hamiltonian
assert np.allclose(
    H_reconstructed, H_reconstructed.conj().T
)

# confirm reconstruction was successful to some given numerical tolerance
assert np.allclose(H_m, H_reconstructed, atol=1e-6)

Instead of performing these checks by hand, we can use the helper function validate_kak().

>>> assert validate_kak(H, g, k, (adjvec_a, theta_opt), n, 1e-6)