Let us consider the following situation, which displays the full potential of the Tridiagonal class: We have a system consisting of subsystems, each featuring a free (in general, non-Hermitian) Hamiltonian, which is diagonal in the given system’s Hilbert space. In addition, we have one more term of a special form in the Hamiltonian coupling all the subsystems (we are considering , because this is what appears in the framework as noted here):
(1)
Here, the coefficients and are in general complex with the dimension of frequency (). The s are the dimensions of the subsystems’ Hilbert spaces in which the vectors form an orthonormal basis.
The Hamiltonian is indeed in a special form because the interaction term is a direct product of such operators acting on the Hilbert spaces of the individual subsystems, whose matrix contains only three diagonals of nonzero elements. Hence the name of the class Tridiagonal, although this usually refers to the case when .
Now let us transform to the interaction picture defined by . The Hamiltonian in interaction picture reads
(2)
where .
Quite generally, the Tridiagonal class is designed to store and manipulate Hamiltonians of the form either (1) or (2) for an arbitrary number of subsystems. In the latter case, it also stores and manipulates the frequencies. In particular, the Hamiltonian can be evaluated at a given time , applied on a state vector and combined with other Tridiagonals using algebraic operations.
Tridiagonal internally bookkeeps to which time instant its state corresponds.
Policy of storing diagonals: only the non-zeros. Policy of storing frequencies: either none or all of them.
Tridiagonal can represent a diagonal matrix without significant overhead.
Note
The frequencies are not necessarily real here, Tridiagonal works also for non-Hermitian operators. On non-unitary transformations in quantum mechanics these notes.
Template argument definitions
template <int RANK> (cf. template parameters); inherits publicly from DimensionsBookkeeper<RANK,false>, and privately from linalg::VectorSpace<Tridiagonal<RANK> > which adds a lot of free-standing arithmetic functions
Types
The number of Diagonals the class has to store:
static const int LENGTH=tmptools::Power<3,RANK>::value;
Reports the class’s rank for example towards DirectProduct.
The class is implemented in terms of a blitzplusplus::TinyOfArrays, this is the class used to store the Diagonals:
typedef blitzplusplus::TinyOfArrays<dcomp,RANK,LENGTH> Diagonals;
typedef typename Diagonals::T_numtype Diagonal;
Inherited from DimensionsBookkeeper
typedef typename quantumdata::Types<RANK>::StateVectorLow StateVectorLow;
(private) used for terser member-function signatures
typedef mpl::int_<RANK> IntRANK;
(private) used for terser member-function signatures
static const mpl::int_<1> _1_;//=mpl::int_<1>();
(private) used for terser member-function signatures
static const Diagonal empty;//=Diagonal();
Constructors, assignment
This is the principal way to create an object of this class, which can be used in the unary case only, as ensured by the trailing dummy argument. This creates an object corresponding to the elementary operator
(3)
when toFreq=false and
(4)
when toFreq=true. The arguments zero, minus, plus, and k correspond respectively to , , , and . In case of toFreq=true, the frequencies are calculated out of as .
Either of the three initializing arrays might be zero-size, which signifies that the corresponding diagonal is zero, however, if they are nonzero-size, then their sizes must be compatible with each other. If both minus and plus are zero-size (purely diagonal matrix), then k might be zero as well. Violation is detected at runtime, and an exception of type TridiagonalConsistencyErrorException is thrown.
Note
It is a dilemma whether the parameter k should be considered a compile-time or a runtime parameter. In the majority of cases it is known already an compile time (e.g. ladder operators, angular momentum operators, etc.). The reason why it is treated as a runtime parameter is spatial degrees of freedom. There, operators like , , etc., are also tridiagonal in momentum space, and we wanted to have to possibility of specifying at runtime.
Copy constructor with deep-copy semantics.
template <int RANK2>
Constructing the object as the direct product of tridiag1 and tridiag2. This is rather non-trivial, and the calculation of the resulting diagonals’ position is at the moment calculated at runtime, though it could partly be done at compile time. The eventual frequencies are also composed, direct product translates to a “direct sum” in this case. This really makes sense only if the time instants of the two tridiagonals are the same. Violation is detected at runtime, and an exception of type TridiagonalTimeMismatchException is thrown.
All tridiagonals of RANK>1 originate from direct products of unary tridiagonals.
Furnishes a unary tridiagonal with frequencies calculated from mainDiagonal. Note that the matrix may have its own main diagonal in this case, which remains untouched. (This is actually exploited if we want to transform out only part of a main diagonal with interaction picture.) Returns *this.
Algebra
Note
At the moment, tridiagonal algebra is meant to be performed in the startup phase of simulations only, therefore, here we do not strive too much to optimize efficiency.
Returns a newly constructed object, which is the Hermitian conjugate of this. Transposition involves a non-trivial permutation of diagonals, which could be done at compile-time, but at the moment it’s runtime.
Frequencies need not be transposed, because they are identical to their transpose.
Note
Eventually, an in-place version of Hermitian conjugation could be also implemented, if need for this arises.
Naive addition. The structures of the two objects must match, and there is a rather complicated set of rules as to what is meant by “matching”. Tridiagonal freely mixes with a purely diagonal matrix throught addition. If both have off-diagonals as well, the ks must match. If both store frequencies, they must be equal. Any violation of these rules is detected at runtime, and an exception of type TridiagonalStructureMismatchException is thrown.
Returns a (deep) copy with negated diagonals. Frequencies remain untouched.
Returns a (deep) copy.
Tridiagonal& operator-=(const Tridiagonal& tridiag) {(*this)+=-tridiag; return *this;}
Naively implemented, could be templated if need arises. Frequencies untouched throughout.
Class-specific functionality
Updates the elements of the matrix to time instant t with the help of the stored frequencies.
“Applies” the tridiagonal matrix on the state vector psiprime, in the vein of structure::Hamiltonian::addContribution(), that is .
Explicit specializations of the function apply(). In this case, explicitly specialized code must be generated with the Python script quantumoperator/applyImpl/generate.py for each RANK (the Python script takes the rank as its first parameter in the command line). The user is encouraged to try it out to see the structure of the problem.
Here, the recursion inherent in the problem is shifted to this Python script. This solution sidesteps template metaprogramming, however, it has the drawback that the amount of code such produced grows exponentially with the arity.
This possibility is used if the DO_CONSIDER_EXPLICITLY_SPECIALIZED_TRIDIAGONAL_APPLIES macro is defined at compilation.
The function is implemented using the recursive doApply(), whose second version is present to break the recursivity. In this case, only this second version of the doApply() function must be explicitly specialized, which results in much less code.
This possibility is used if the DO_CONSIDER_EXPLICITLY_SPECIALIZED_TRIDIAGONAL_APPLIES macro is not defined at compilation.
Note
(Perhaps somewhat surprisingly,) compile-time resource requirement is larger in the 1st case.
The following four entities are private, serving as helpers for apply() in the 2nd case:
template<int START, typename V_DPSIDT, typename V_A, typename V_PSI, int REMAINING>
template<int START, typename V_DPSIDT, typename V_A, typename V_PSI>
This is explicitly specialized for all RANKs, with the help of preprocessor metaprogramming. We rely on the Boost.Preprocessor library, in particular the BOOST_PP_ITERATE macro. The reentrant header can be found at quantumoperator/details/TridiagonalApplySpecialization.h.
Free-standing helpers
At the moment only for the unary case, implemented in terms of quantumoperator::Tridiagonal::furnishWithFreqs().
Note
A serious limitation of Tridiagonal is that the composition of two such operators does not in general yield an operator of the same form. This is one of the reasons why we are planning to deprecate Tridiagonal in favour of a much more general form
with an arbitrary set, and .
template<int L, int R>
Stateless class implementing the unary quantumoperator
The template parameters are eventually converted to runtime data when applying the operator. At that point, dimensionality errors are detected in debug mode. All members are fairly trivial:
static const int N_RANK=1;
typedef quantumdata::Types<1>::StateVectorLow
{dpsidt(L)+=psi(R);}
{return Sigma<R,L>();}
Note
Sigma should perhaps inherit from DimensionsBookkeeper—this would allow for an earlier detection of dimensionality errors.
template<int L, int R, typename OTHER, bool IS_HEAD>
This class is a direct-product clause, representing a germinal expression-template mechanism for direct products of Sigma<L,R> with OTHER types.
IS_HEAD signifies whether Sigma<L,R> is at the head or at the tail of the direct product.
Reports the rank of the class for recursive usage:
static const int N_RANK=OTHER::N_RANK+1;
typedef typename quantumdata::Types<N_RANK>::StateVectorLow StateVectorLow;
The class has to store a reference to the OTHER class to enable apply()ing.
Applies the clause on a state vector psi, adding the result to dpsidt, analogously to quantumoperator::Tridiagonal::apply() and structure::Hamiltonian::addContribution(). It is implemented as taking the partial projection of the state vectors according to L and R (which at this point are converted into runtime data), and calling the apply function of the OTHER type.
Free-standing helpers
template<int L, int R, typename OTHER>
template<int L, int R, typename OTHER>
template<int L1, int R1, int L2, int R2>
These operators aptly demonstrate the use of DirectProduct and the meaning of its template parameters:
template<int RANK, bool IS_HEAD>
Helper for quantumoperator::DirectProduct::apply(). Calculates the state-vector slice
if IS_HEAD=true and
if IS_HEAD=false.
The code is automatically generated for all template-parameter combinations (RANK up to 11) via preprocessor metaprogramming. Cf. quantumoperator::Sigma.cc:
g++ -P -E -Iutils/include/ -Iquantumoperator/ -Iquantumdata/ quantumoperator/Sigma.cc | tail -n128