# 4. Calculating Quantities¶

There are various built-in functions to calculate quantities, not limited to:

## 4.1. Approximate Spectral Functions¶

The module approx_spectral, contains a Lanczos method for estimating any quantities of the form tr(fn(A)). Where A is any operator that implements a dot product with a vector. For example, estimating the trace of the sqrt of a matrix would naievly require diagonalising it:

:

import quimb as qu

rho = qu.rand_rho(2**12)
rho_el = qu.eigvalsh(rho)
sum(rho_el ** 0.5)

:

54.32407060007722

:

qu.tr_sqrt_approx(rho)

:

54.52284527680732


Diagonalization has a cost of O(n^3), which is essentially reduced to O(k * n^2) for this stochastic method. For a general function approx_spectral_function() can be used.

However, the real advantage occurs when the full matrix does not need to be fully represented, e.g. in the case of ‘partial trace states’. One can then calculate quantities for subsystems that would not be possible to explicitly represent.

For example, the partial trace, followed by partial transpose, followed by vector multiplication can be ‘lazily’ evaluated as a tensor contraction (see lazy_ptr_ppt_dot()). In this way the logarithmic negativity of subsytems can be efficiently calculated:

:

psi = qu.rand_ket(2**20)
dims = [2**8, 2**4, 2**8]
qu.logneg_subsys_approx(psi, dims, sysa=0, sysb=2)

:

5.737062140763265


Under the hood, this method makes use of quimb.tensor functionality, which allows various tensor contraction backends to be used (see set_tensor_linop_backend()). These types of computation are particularly suited to the GPU and therefore if cupy is installed it will be used automatically:

:

%timeit qu.logneg_subsys_approx(psi, dims, sysa=0, sysb=2, backend='numpy')

799 ms ± 118 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

:

%timeit qu.logneg_subsys_approx(psi, dims, sysa=0, sysb=2, backend='cupy')

89.7 ms ± 2.87 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)