https://github.com/ITensor/ITensor
Raw File
Tip revision: 7c0a20602668796c706c5911d4203582d6bed9e6 authored by Miles Stoudenmire on 22 October 2013, 11:46:06 UTC
Fixed complex case of tieIndices, fixes #46.
Tip revision: 7c0a206
svdalgs.h
//
// Distributed under the ITensor Library License, Version 1.0.
//    (See accompanying LICENSE file.)
//
#ifndef __ITENSOR_SVDALGS_H
#define __ITENSOR_SVDALGS_H
#include "iqcombiner.h"
#include "iqtsparse.h"
#include "localop.h"
#include "spectrum.h"

#define Cout std::cout
#define Endl std::endl
#define Format boost::format


//
// Singular value decomposition (SVD)
//
// Factors a tensor AA such that AA=U*D*V
// with D diagonal, real, and non-negative.
//
template<class Tensor, class SparseT>
Spectrum
svd(const Tensor& AA, Tensor& U, SparseT& D, Tensor& V,
    const OptSet& opts = Global::opts())
    {
    Spectrum spec;
    svd(AA,U,D,V,spec,opts);
    return spec;
    }

//SVD with Spectrum object controlling truncation
//and storing eigs (squares of singular values) on return
template<class Tensor, class SparseT>
void 
svd(Tensor AA, Tensor& U, SparseT& D, Tensor& V, 
    Spectrum& spec,
    const OptSet& opts = Global::opts());

//
// Density Matrix Decomposition
// 
// Factors a tensor AA such that AA = A*B.
// Result is equivalent to SVD such that AA = U*D*V where if
// dir==Fromleft, A=U and B=(D*V) or, if dir==Fromright, A=(U*D) and B=V.
// Implementation is faster than SVD, though, and allows the
// noise term to be used.
//
// To determine which indices end up on which factors (i.e. on A versus B),
// the method examines the initial indices of A and B.
// If a given index is present on, say, A, then it will on A 
// upon return (although the elements of A will be overwritten and other indices
// may be added to it). Any indices not initially present on A or B 
// will end up on B if dir==Fromleft or on A if dir==Fromright.
//
template<class Tensor>
Spectrum 
denmatDecomp(const Tensor& AA, Tensor& A, Tensor& B, Direction dir, 
             const OptSet& opts = Global::opts())
    {
    Spectrum spec;
    denmatDecomp(AA,A,B,dir,spec,LocalOp<Tensor>::Null(),opts);
    return spec;
    }


//Density matrix decomp with a Spectrum object controlling
//the truncation parameters and storing the eigs on return.
template<class Tensor>
void 
denmatDecomp(const Tensor& AA, Tensor& A, Tensor& B, Direction dir, 
             Spectrum& spec,
             const OptSet& opts = Global::opts())
    {
    denmatDecomp(AA,A,B,dir,spec,LocalOp<Tensor>::Null(),opts);
    }

//Density matrix decomp with a Spectrum object 
//and LocalOpT object supporting the noise term
//The LocalOpT argument PH has to provide the deltaRho method
//to enable the noise term feature (see localop.h for example)
template<class Tensor, class LocalOpT>
void 
denmatDecomp(const Tensor& AA, Tensor& A, Tensor& B, Direction dir, 
             Spectrum& spec, const LocalOpT& PH,
             const OptSet& opts = Global::opts());



//
// Hermitian eigenvalue decomposition / diagonalization
//
// Assumes input is a Hermitian tensor with indices
// i,j,k,.... and i',j',k',...
// (tensor must be conjugate symmetric under
//  exchange primed and unprimed indices)
// Result is unitary tensor U and diagonal sparse tensor D
// such that M == conj(U)*D*primed(U)
//
template<class Tensor, class SparseT>
Spectrum 
diagHermitian(const Tensor& M, Tensor& U, SparseT& D,
              const OptSet& opts = Global::opts())
    {
    Spectrum spec;
    diagHermitian(M,U,D,spec,opts);
    return spec;
    }

//Version accepting a Spectrum object which
//stores eigs on return
template<class Tensor, class SparseT>
void 
diagHermitian(const Tensor& M, Tensor& U, SparseT& D, 
              Spectrum& spec,
              const OptSet& opts = Global::opts());




//
// Orthogonal decomposition
//
// Given a tensor T, decomposes it into two tensors A and B
// such that T=A*B. If dir==Fromleft, A is guaranteed to be
// real and orthogonal, similar for B if dir==Fromright.
//
template<class Tensor>
Spectrum 
orthoDecomp(const Tensor& T, Tensor& A, Tensor& B, Direction dir, 
            const OptSet& opts = Global::opts())
    {
    Spectrum spec;
    orthoDecomp(T,A,B,dir,spec,opts);
    return spec;
    }

template<class Tensor>
void 
orthoDecomp(Tensor T, Tensor& A, Tensor& B, Direction dir, 
            Spectrum& spec,
            const OptSet& opts = Global::opts());




//
// Inverse Canonical SVD
//
// Factors a tensor AA such that AA=L*V*R
// where V is the inverse of the diagonal tensor
// appearing in the SVD
//
template<class Tensor, class SparseT>
Spectrum 
csvd(const Tensor& AA, Tensor& L, SparseT& V, Tensor& R, 
     const OptSet& opts = Global::opts())
    {
    Spectrum spec;
    csvd(AA,L,V,R,spec,opts);
    return spec;
    }

template<class Tensor, class SparseT>
void 
csvd(const Tensor& AA, Tensor& L, SparseT& V, Tensor& R, 
     Spectrum& spec, 
     const OptSet& opts = Global::opts());


///////////////////////////
//
// Implementation (non-template parts in svdalgs.cc)
//
//////////////////////////


void 
svdRank2(ITensor A, const Index& ui, const Index& vi,
         ITensor& U, ITSparse& D, ITensor& V, Spectrum& spec,
         const OptSet& opts = Global::opts());

void 
svdRank2(IQTensor A, const IQIndex& uI, const IQIndex& vI,
         IQTensor& U, IQTSparse& D, IQTensor& V, Spectrum& spec,
         const OptSet& opts = Global::opts());

template<class Tensor, class SparseT>
void 
svd(Tensor AA, Tensor& U, SparseT& D, Tensor& V, 
    Spectrum& spec, 
    const OptSet& opts)
    {
    typedef typename Tensor::IndexT 
    IndexT;
    typedef typename Tensor::CombinerT 
    CombinerT;
    
    if(isZero(AA,Opt("Fast"))) 
        throw ResultIsZero("svd: AA is zero");

    if(spec.noise() > 0)
        Error("Noise term not implemented for svd");

    //if(noise_ > 0 && !PH.isNull())
    //    {
    //    //Add in noise term
    //    Real orig_norm = AA.norm();
    //    AA += noise_*PH.deltaPhi(AA);
    //    AA *= orig_norm/AA.norm();
    //    }

    //Combiners which transform AA
    //into a rank 2 tensor
    CombinerT Ucomb, Vcomb;
    Ucomb.doCondense(true);
    Vcomb.doCondense(true);


    //Divide up indices based on U
    //If U is null, use V instead
    const Tensor &L = (U.isNull() ? V : U);
    CombinerT &Lcomb = (U.isNull() ? Vcomb : Ucomb),
              &Rcomb = (U.isNull() ? Ucomb : Vcomb);
    Foreach(const IndexT& I, AA.indices())
        { 
        if(hasindex(L,I))
            Lcomb.addleft(I);
        else
            Rcomb.addleft(I);
        }

    AA = Ucomb * AA * Vcomb;

    const Real saved_cutoff = spec.cutoff(); 
    const int saved_minm = spec.minm(),
              saved_maxm = spec.maxm(); 
    if(spec.useOrigM())
        {
        //Try to determine current m,
        //then set minm_ and maxm_ to this.
        spec.cutoff(-1);
        if(D.r() == 0)
            {
            try {
                IndexT mid = commonIndex(U,V,Link);
                spec.minm(mid.m());
                spec.maxm(mid.m());
                }
            catch(const ITError& e)
                {
                spec.minm(1);
                spec.maxm(1);
                }
            }
        else
            {
            spec.minm(D.index(1).m());
            spec.maxm(D.index(1).m());
            }
        }

    svdRank2(AA,Ucomb.right(),Vcomb.right(),U,D,V,spec,opts);

    spec.cutoff(saved_cutoff);
    spec.minm(saved_minm);
    spec.maxm(saved_maxm);

    U = conj(Ucomb) * U;
    V = V * conj(Vcomb);

    } //svd

template<class Tensor, class SparseT>
void 
csvd(const Tensor& AA, Tensor& L, SparseT& V, Tensor& R, 
     Spectrum& spec, 
     const OptSet& opts)
    {
    typedef typename Tensor::IndexT 
    IndexT;
    typedef typename Tensor::CombinerT 
    CombinerT;

    Tensor UU(L),VV(R);
    SparseT D(V);
    svd(AA,UU,D,VV,spec,opts);

    L = UU*D;
    R = D*VV;

    V = conj(D);
    V.pseudoInvert(0);
    }

Real 
diag_hermitian(ITensor rho, ITensor& U, ITSparse& D, Spectrum& spec,
               const OptSet& opts = Global::opts());

Real 
diag_hermitian(IQTensor rho, IQTensor& U, IQTSparse& D, Spectrum& spec,
               const OptSet& opts = Global::opts());

template<class Tensor, class LocalOpT>
void 
denmatDecomp(const Tensor& AA, Tensor& A, Tensor& B, Direction dir, 
             Spectrum& spec, const LocalOpT& PH,
             const OptSet& opts)
    {
    typedef typename Tensor::IndexT 
    IndexT;
    typedef typename Tensor::CombinerT 
    CombinerT;
    typedef typename Tensor::SparseT 
    SparseT;

    if(isZero(AA,Opt("Fast"))) 
        {
        throw ResultIsZero("denmatDecomp: AA is zero");
        }

    IndexT mid; 
    try {
        mid = commonIndex(A,B,Link);
        }
    catch(const ITError& e)
        {
        //continue, leaving mid default-initialized
        }

    //If dir==None, put the O.C. on the side
    //that keeps mid's arrow the same
    if(dir == None)
        {
        //Cout << Format("Arrow before = %s")%(mid.dir() == Out ? "Out" : "In") << Endl;
        dir = (mid.dir() == Out ? Fromright : Fromleft);
        }

    Tensor& to_orth = (dir==Fromleft ? A : B);
    Tensor& newoc   = (dir==Fromleft ? B : A);
    
    CombinerT comb;

    const IndexSet<IndexT>& activeInds = (to_orth.isNull() ? AA : to_orth).indices();

    Foreach(const IndexT& I, activeInds)
        { 
        if(!hasindex(newoc,I))
            comb.addleft(I);
        }

    //Apply combiner
    comb.doCondense(true);
    comb.init(mid.isNull() ? "mid" : mid.rawname());

    Tensor AAc; 
    comb.product(AA,AAc);

    //Form density matrix
    Tensor AAcc = conj(AAc); 
    AAcc.prime(comb.right()); 

    Tensor rho = AAc*AAcc; 

    //Add noise term if requested
    if(spec.noise() > 0 && !PH.isNull())
        {
        rho += spec.noise()*PH.deltaRho(AA,comb,dir);
        rho *= 1./trace(realPart(rho));
        }

    const Real saved_cutoff = spec.cutoff(); 
    const int saved_minm = spec.minm(),
              saved_maxm = spec.maxm(); 
    if(spec.useOrigM())
        {
        spec.cutoff(-1);
        spec.minm(mid.m());
        spec.maxm(mid.m());
        }

    if(opts.getBool("TraceReIm",false))
        {
        rho = realPart(rho);
        }

    Tensor U;
    SparseT D;
    Real truncerr = diag_hermitian(rho,U,D,spec,opts);
    spec.truncerr(truncerr);

    spec.cutoff(saved_cutoff);
    spec.minm(saved_minm);
    spec.maxm(saved_maxm);

    comb.conj();
    comb.product(conj(U),to_orth);
    newoc = U * AAc;

    } //denmatDecomp



template<class Tensor, class SparseT>
void 
diagHermitian(const Tensor& M, Tensor& U, SparseT& D, Spectrum& spec,
              const OptSet& opts)
    {
    typedef typename Tensor::IndexT 
    IndexT;
    typedef typename Tensor::CombinerT 
    CombinerT;

    if(isZero(M,Opt("Fast"))) 
        throw ResultIsZero("denmatDecomp: M is zero");

    CombinerT comb;
    Foreach(const IndexT& I, M.indices())
        { 
        if(I.primeLevel() == 0)
            {
            comb.addleft(I);
            }
        }

    //Apply combiner
    comb.doCondense(true);
    comb.init("d");

    Tensor Mc; 
    comb.product(M,Mc);

    CombinerT combP(comb);
    combP.prime();
    combP.conj();

    try {
        Mc = combP * Mc;
        }
    catch(const ITError& e)
        {
        Cout << "Diagonalize expects opposite arrow directions for primed and unprimed indices." << Endl;
        throw e;
        }

    const bool truncate_setting = spec.truncate();
    spec.truncate(false);
    diag_hermitian(Mc,U,D,spec,opts);
    spec.truncerr(0);
    spec.truncate(truncate_setting);

    U = comb * U;

    } //diagHermitian


template<class Tensor>
void 
orthoDecomp(Tensor T, Tensor& A, Tensor& B, Direction dir, 
            Spectrum& spec,
            const OptSet& opts)
    {
    typedef typename Tensor::IndexT 
    IndexT;
    typedef typename Tensor::CombinerT 
    CombinerT;
    typedef typename Tensor::SparseT
    SparseT;

    if(isZero(T,Opt("Fast"))) 
        throw ResultIsZero("orthoDecomp: T is zero");

    const Real orig_noise = spec.noise();
    spec.noise(0);

    const
    bool usedenmat = false;

    if(usedenmat)
        {
        denmatDecomp(T,A,B,dir,spec,opts & Opt("TraceReIm"));
        }
    else //use svd
        {
        //Combiners which transform T
        //into a rank 2 tensor
        CombinerT Acomb, Bcomb;
        Acomb.doCondense(true);
        Bcomb.doCondense(true);

        const
        IndexT reim = IQIndex("ReIm",Index("reim",2),QN());

        //Divide up indices based on U
        //If U is null, use V instead
        const Tensor &L = (A.isNull() ? B : A);
        CombinerT &Lcomb = (A.isNull() ? Bcomb : Acomb),
                  &Rcomb = (A.isNull() ? Acomb : Bcomb);
        Foreach(const IndexT& I, T.indices())
            { 
            if(hasindex(L,I))
                Lcomb.addleft(I);
            else
                Rcomb.addleft(I);
            }

        if(dir == Fromleft)
            Rcomb.addleft(reim);
        else
            Lcomb.addleft(reim);

        T = realPart(T)*reim(1) + imagPart(T)*reim(2);

        T = Acomb * T * Bcomb;


        SparseT D;
        svdRank2(T,Acomb.right(),Bcomb.right(),A,D,B,spec,opts);

        A = conj(Acomb) * A;
        B = B * conj(Bcomb);

        if(dir==Fromleft) 
            {
            B *= D;
            B = B*conj(reim)(1) + Complex_i*B*conj(reim)(2);
            }
        else              
            {
            A *= D;
            A = A*conj(reim)(1) + Complex_i*A*conj(reim)(2);
            }
        }

    spec.noise(orig_noise);
    } //orthoDecomp


#undef Cout
#undef Format
#undef Endl


#endif
back to top