#
# This file is part of TensorToolbox.
#
# TensorToolbox is free software: you can redistribute it and/or modify
# it under the terms of the LGNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# TensorToolbox is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# LGNU Lesser General Public License for more details.
#
# You should have received a copy of the LGNU Lesser General Public License
# along with TensorToolbox. If not, see <http://www.gnu.org/licenses/>.
#
# DTU UQ Library
# Copyright (C) 2014 The Technical University of Denmark
# Scientific Computing Section
# Department of Applied Mathematics and Computer Science
#
# Author: Daniele Bigoni
#
__all__ = ['QTTmat']
import logging
import operator
import numpy as np
from TensorToolbox.core import QTTvec
from TensorToolbox.core import TTmat
from TensorToolbox.core import idxfold, idxunfold
[docs]class QTTmat(TTmat):
""" Constructor of multidimensional matrix in Quantics Tensor Train format
:param ndarray,TT A: Available input formats are full tensor in numpy.ndarray, Tensor Train structure (list of cores). If input is ndarray, then it must be in mattensor format (see aux.py)
:param int base: folding base for QTT representation
:param int nrows: If int then the row size will be the same in all dimensions, if list then len(nrows) == len(self.TT) (numer of cores) and row size will change for each dimension.
:param int ncols: If int then the column size will be the same in all dimensions, if list then len(ncols) == len(self.TT) (numer of cores) and column size will change for each dimension.
"""
logger = logging.getLogger(__name__)
def __init__(self,A,base,nrows,ncols,is_sparse=None,
store_location="",store_object=None,store_freq=1, store_overwrite=False):
super(QTTmat,self).__init__(A, base, base, is_sparse=is_sparse,
store_location=store_location,
store_object=store_object,
store_freq=store_freq,
store_overwrite=store_overwrite)
######################################
# List of attributes
self.base = None
self.basemat = None
self.L = None
self.full_nrows = [] # Real sizes of the tensor matrices
self.full_ncols = [] # Real sizes of the tensor matrices
self.serialize_list.extend( ['base', 'basemat', 'L', 'full_nrows', 'full_ncols'] )
# End list of attributes
######################################
self.full_nrows = nrows
self.full_ncols = ncols
self.base = base
self.basemat = base**2
def __getstate__(self):
return super(QTTmat,self).__getstate__()
def __setstate__(self,state):
super(QTTmat,self).__setstate__( state )
[docs] def build(self, eps=1e-10, method='svd', rs=None, fix_rank=False, Jinit=None, delta=1e-4, maxit=100, mv_eps=1e-6, mv_maxit=100, kickrank=2):
""" Common interface for the construction of the approximation.
:param float eps: [default == 1e-10] For method=='svd': precision with which to approximate the input tensor. For method=='ttcross': TT-rounding tolerance for rank-check.
:param string method: 'svd' use singular value decomposition to construct the TT representation [1]_, 'ttcross' use low rank skeleton approximation to construct the TT representation [2]_, 'ttdmrg' uses Tensor Train Renormalization Cross to construct the TT representation [3]_ [4]_, 'ttdmrgcross' uses 'ttdmrg' with 'ttcross' approximation of supercores
:param list rs: list of integer ranks of different cores. If ``None`` then the incremental TTcross approach will be used. (method=='ttcross')
:param bool fix_rank: determines whether the rank is allowed to be increased (method=='ttcross')
:param list Jinit: list of list of integers containing the r starting columns in the lowrankapprox routine for each core. If ``None`` then pick them randomly. (method=='ttcross')
:param float delta: accuracy parameter in the TT-cross routine (method=='ttcross'). It is the relative error in Frobenious norm between two successive iterations.
:param int maxit: maximum number of iterations in the lowrankapprox routine (method=='ttcross')
:param float mv_eps: accuracy parameter for each usage of the maxvol algorithm (method=='ttcross')
:param int mv_maxit: maximum number of iterations in the maxvol routine (method=='ttcross')
:param bool fix_rank: Whether the rank is allowed to increase
:param int kickrank: rank overshooting for 'ttdmrg'
"""
nrows = self.full_nrows
ncols = self.full_ncols
if isinstance(nrows,int) and isinstance(ncols,int):
nrows = [nrows]
ncols = [ncols]
if len(nrows) != len(ncols): raise NameError("TensorToolbox.QTTmat.__init__: len(nrows)!=len(ncols)")
self.full_nrows = nrows
self.full_ncols = ncols
if isinstance(self.A,np.ndarray):
for i,sizedim in enumerate(self.A.shape):
if sizedim != self.full_nrows[i] * self.full_ncols[i]:
raise NameError("TensorToolbox.QTTmat.__init__: Array dimension not consistent with nrows and ncols")
if np.remainder(np.log(sizedim)/np.log(self.basemat),1.0) > np.spacing(1):
raise NameError("TensorToolbox.QTTmat.__init__: base is not a valid base of A.size")
self.L = int( np.log(self.A.size)/np.log(self.basemat) )
# Prepare interleaved idxs (wtf!!!)
Ls = [ int( np.log(self.full_nrows[i]*self.full_ncols[i])/np.log(self.basemat) ) for i in range(self.ndims()) ]
idxs = []
for j in range(self.ndims()):
offset = np.sum(2 * Ls[:j],dtype=int)
for i in range(Ls[j]):
idxs.append(offset + i)
idxs.append(offset + Ls[j]+i)
# Fold, re-order and reshape
self.A = np.reshape(self.A,[self.base for i in range(2*self.L)])
self.A = np.transpose(self.A,axes=idxs)
self.A = np.reshape(self.A,[self.basemat for i in range(self.L)])
super(QTTmat,self).build( eps, method, rs, fix_rank, Jinit, delta, maxit, mv_eps, mv_maxit, kickrank )
elif isinstance(self.A,list):
super(QTTmat,self).build( eps, method, rs, fix_rank, Jinit, delta, maxit, mv_eps, mv_maxit, kickrank )
# Check that unfolded nrows,ncols are consistent with the dimension of A
if np.prod(self.shape()) != np.prod(self.full_nrows)*np.prod(self.full_ncols):
self.init = False
raise NameError("TensorToolbox.QTTmat.__init__: unfolded nrows,ncols not consistent with shape of A")
for nrow,ncol in zip(self.full_nrows,self.full_ncols):
if np.remainder(np.log(nrow*ncol)/np.log(self.basemat),1.0) > np.spacing(1):
self.init = False
raise NameError("TensorToolbox.QTTmat.__init__: base is not a valid base for the selected nrows,ncols")
self.L = len(self.shape())
return self
[docs] def ndims(self):
""" Return the number of dimensions of the tensor space
"""
return len(self.full_nrows)
[docs] def get_full_nrows(self):
""" Returns the number of rows of the unfolded matrices
"""
return self.full_nrows
[docs] def get_full_ncols(self):
""" Returns the number of cols of the unfolded matrices
"""
return self.full_ncols
[docs] def get_nrows(self):
""" Returns the number of rows of the folded matrices
"""
return self.nrows
[docs] def get_ncols(self):
""" Returns the number of cols of the folded matrices
"""
return self.nrows
def copy(self):
newTT = []
for TTi in self.TT: newTT.append(TTi.copy())
return QTTmat(newTT,self.base,nrows=list(self.get_full_nrows()),ncols=list(self.get_full_ncols())).build()
def __getitem__(self,idxs):
""" Get item function
:param tuple,int idxs: ((i_1,..,i_d),(j_1,..,j_d)) with respect to the unfolded mode sizes
:returns: item at that position
"""
if not self.init: raise NameError("TensorToolbox.QTTmat.__getitem__: QTT not initialized correctly")
# Check for out of bounds
if any(map(operator.ge,idxs[0],self.get_full_nrows())) or any(map(operator.ge,idxs[1],self.get_full_ncols())):
raise NameError("TensorToolbox.QTTmat.__getitem__: Index out of bounds")
# Compute the index of the folding representation from the unfolded index
return TTmat.__getitem__(self,
( idxfold( self.get_nrows(), idxunfold(self.get_full_nrows(), idxs[0])),
idxfold( self.get_ncols(), idxunfold(self.get_full_ncols(), idxs[1]))) )
def kron(self,A):
if not self.init: raise NameError("TensorToolbox.QTTmat.kron: TT not initialized correctly")
# Additional tests wrt the extend function of TTvec
if not isinstance(A,QTTmat): raise NameError("TensorToolbox.QTTmat.kron: A is not of QTTmat type")
if not A.init: raise NameError("TensorToolbox.QTTmat.kron: input tensor is not initialized correctly")
if self.base != A.base: raise NameError("TensorToolbox.QTTmat.kron: kron product for QTT vectors is allowed only for the same bases")
super(QTTmat,self).kron(A)
self.full_nrows.extend(A.get_full_nrows())
self.full_ncols.extend(A.get_full_ncols())
self.L = len(self.shape())
def dot(self,B):
out = super(QTTmat,self).dot(B)
if isinstance(B,QTTmat):
out = QTTmat( out.TT, base=self.base,
nrows=self.get_full_nrows(), ncols=B.get_full_ncols() )
out.build()
elif isinstance(B,QTTvec):
out = QTTvec( out.TT, global_shape=self.get_full_nrows() )
out.build()
elif not isinstance(B,np.ndarray):
raise AttributeError("TensorToolbox.QTTmat.dot: wrong input format")
return out