# Path: ffthompy/trigpol.py
# class Grid():
#     @staticmethod
#     def get_ZNl(N, fft_form=fft_form_default):
#         r"""
#         it produces index set ZNl=\underline{\set{Z}}^d_N :
#         ZNl[i][j]\in\set{Z} : -N[i]/2 <= ZNl[i] < N[i]/2
#         """
#         ZNl = []
#         N = np.atleast_1d(np.array(N, dtype=np.int))
#         for m in range(N.size):
#             ZNl.append(np.arange(np.fix(-N[m]/2.), np.fix(N[m]/2.+0.5),
#                                  dtype=np.int))
#         if fft_form in ['r',0]:
#             return [np.fft.ifftshift(val) for val in ZNl]
#         else:
#             return ZNl
# 
#     @staticmethod
#     def get_xil(N, Y, fft_form=fft_form_default):
#         """
#         it produces discrete frequencies of Fourier series
#         xil[i] = ZNl[i]/Y[i]
#         """
#         xil = []
#         for m in np.arange(np.size(N)):
#             xil.append(np.arange(np.fix(-N[m]/2.), np.fix(N[m]/2.+0.5))/Y[m])
#         if fft_form in ['r',]:
#             xil=[np.fft.ifftshift(xi) for xi in xil]
#             xil[-1] = xil[-1][:int(np.fix(N[-1]/2)+1)]
#         elif fft_form in [0]:
#             xil = [np.fft.ifftshift(xi) for xi in xil]
#         return xil
# 
#     @staticmethod
#     def get_freq(N, Y, fft_form=fft_form_default):
#         return Grid.get_xil(N, Y, fft_form=fft_form)
# 
#     @staticmethod
#     def get_product(xi):
#         xis = np.atleast_2d(xi[0])
#         for ii in range(1, len(xi)):
#             xis_new = np.tile(xi[ii], xis.shape[1])
#             xis_old = np.repeat(xis, xi[ii].size, axis=1)
#             xis = np.vstack([xis_old, xis_new])
#         return xis
# 
#     @staticmethod
#     def get_coordinates(N, Y):
#         """
#         It produces coordinates of the set of nodal points
#         Coord[i][j] = x_N^{(i,j)}
#         """
#         d = np.size(N)
#         ZNl = Grid.get_ZNl(N, fft_form='c')
#         coord = np.zeros(np.hstack([d, N]))
#         for ii in np.arange(d):
#             x = Y[ii]*ZNl[ii]/N[ii]
#             Nshape = np.ones(d, dtype=np.int)
#             Nshape[ii] = N[ii]
#             Nrep = np.copy(N)
#             Nrep[ii] = 1
#             coord[ii] = np.tile(np.reshape(x, Nshape), Nrep)
#         return coord
# 
# Path: ffthompy/tensorsLowRank/objects/sparseTensorWrapper.py
# def SparseTensor(kind='tt', val=None, core=None, basis=None, eps=None, rank=None,
#                  Fourier=False, name='unnamed', vectorObj=None, fft_form=fft_form_default):
#     """
#     A uniform wrapper of different tensorsLowRank tensor format
# 
#     :param kind: type of tensorsLowRank tensor, can be 'cano','tucker' or 'tt', or more variants (see the code).
#     :type kind: string
# 
#     :param val: a full tensor to be approximated
#     :type val: n-D array
# 
#     :param core: core for canonical, tucker or TT tensorsLowRank tensor
#     :type core: 1-D array for canonical tensor, n-D arary for tucker, list of arrays for TT.
# 
#     :param basis: basis for canonical or tucker tensorsLowRank tensor.
#     :type basis: list of arrays.
# 
#     :param eps: approximation accuracy.
#     :type eps: float.
# 
#     :param rank: rank of the cano and tucker tensorsLowRank tensor, maximum rank of TT tensorsLowRank tensor.
#     :type rank: int for cano and TT, list of int for tucker.
# 
#     :param vectorObj: a TTPY vector class object, to be cast into tensorTrain object.
#     :type vectorObj:  TTPY vector
# 
#     :returns: object of tensorsLowRank tensor.
#     """
#     if type(rank) is list or type(rank) is np.ndarray:
#         rmax=max(rank)
#         r=min(rank)
#     else:
#         rmax=r=rank
# 
#     if kind.lower() in ['cano', 'canotensor']:
#         return CanoTensor(name=name, val=val, core=core,basis=basis,Fourier=Fourier,fft_form=fft_form).truncate(rank=r, tol=eps)
#     elif kind.lower() in ['tucker']:
#         return Tucker(name=name, val=val, core=core, basis=basis,Fourier=Fourier,fft_form=fft_form).truncate(rank=rank, tol=eps)
#     elif kind.lower() in ['tt', 'tensortrain']:
#         return TensorTrain(val=val, core=core, eps=eps, rmax=rmax, name=name,
#                            Fourier=Fourier, vectorObj=vectorObj,fft_form=fft_form)
#     else:
#         raise ValueError("Unexpected argument value: '" + kind +"'")


import numpy as np
from ffthompy.trigpol import Grid
from ffthompy.tensorsLowRank.objects import SparseTensor

def grad_tensor(N, Y, kind='TensorTrain'):
    assert(kind.lower() in ['cano','canotensor','tucker','tt','tensortrain'])

    dim=Y.size

# Path: ffthompy/trigpol.py
# class Grid():
#     @staticmethod
#     def get_ZNl(N, fft_form=fft_form_default):
#         r"""
#         it produces index set ZNl=\underline{\set{Z}}^d_N :
#         ZNl[i][j]\in\set{Z} : -N[i]/2 <= ZNl[i] < N[i]/2
#         """
#         ZNl = []
#         N = np.atleast_1d(np.array(N, dtype=np.int))
#         for m in range(N.size):
#             ZNl.append(np.arange(np.fix(-N[m]/2.), np.fix(N[m]/2.+0.5),
#                                  dtype=np.int))
#         if fft_form in ['r',0]:
#             return [np.fft.ifftshift(val) for val in ZNl]
#         else:
#             return ZNl
# 
#     @staticmethod
#     def get_xil(N, Y, fft_form=fft_form_default):
#         """
#         it produces discrete frequencies of Fourier series
#         xil[i] = ZNl[i]/Y[i]
#         """
#         xil = []
#         for m in np.arange(np.size(N)):
#             xil.append(np.arange(np.fix(-N[m]/2.), np.fix(N[m]/2.+0.5))/Y[m])
#         if fft_form in ['r',]:
#             xil=[np.fft.ifftshift(xi) for xi in xil]
#             xil[-1] = xil[-1][:int(np.fix(N[-1]/2)+1)]
#         elif fft_form in [0]:
#             xil = [np.fft.ifftshift(xi) for xi in xil]
#         return xil
# 
#     @staticmethod
#     def get_freq(N, Y, fft_form=fft_form_default):
#         return Grid.get_xil(N, Y, fft_form=fft_form)
# 
#     @staticmethod
#     def get_product(xi):
#         xis = np.atleast_2d(xi[0])
#         for ii in range(1, len(xi)):
#             xis_new = np.tile(xi[ii], xis.shape[1])
#             xis_old = np.repeat(xis, xi[ii].size, axis=1)
#             xis = np.vstack([xis_old, xis_new])
#         return xis
# 
#     @staticmethod
#     def get_coordinates(N, Y):
#         """
#         It produces coordinates of the set of nodal points
#         Coord[i][j] = x_N^{(i,j)}
#         """
#         d = np.size(N)
#         ZNl = Grid.get_ZNl(N, fft_form='c')
#         coord = np.zeros(np.hstack([d, N]))
#         for ii in np.arange(d):
#             x = Y[ii]*ZNl[ii]/N[ii]
#             Nshape = np.ones(d, dtype=np.int)
#             Nshape[ii] = N[ii]
#             Nrep = np.copy(N)
#             Nrep[ii] = 1
#             coord[ii] = np.tile(np.reshape(x, Nshape), Nrep)
#         return coord
# 
# Path: ffthompy/tensorsLowRank/objects/sparseTensorWrapper.py
# def SparseTensor(kind='tt', val=None, core=None, basis=None, eps=None, rank=None,
#                  Fourier=False, name='unnamed', vectorObj=None, fft_form=fft_form_default):
#     """
#     A uniform wrapper of different tensorsLowRank tensor format
# 
#     :param kind: type of tensorsLowRank tensor, can be 'cano','tucker' or 'tt', or more variants (see the code).
#     :type kind: string
# 
#     :param val: a full tensor to be approximated
#     :type val: n-D array
# 
#     :param core: core for canonical, tucker or TT tensorsLowRank tensor
#     :type core: 1-D array for canonical tensor, n-D arary for tucker, list of arrays for TT.
# 
#     :param basis: basis for canonical or tucker tensorsLowRank tensor.
#     :type basis: list of arrays.
# 
#     :param eps: approximation accuracy.
#     :type eps: float.
# 
#     :param rank: rank of the cano and tucker tensorsLowRank tensor, maximum rank of TT tensorsLowRank tensor.
#     :type rank: int for cano and TT, list of int for tucker.
# 
#     :param vectorObj: a TTPY vector class object, to be cast into tensorTrain object.
#     :type vectorObj:  TTPY vector
# 
#     :returns: object of tensorsLowRank tensor.
#     """
#     if type(rank) is list or type(rank) is np.ndarray:
#         rmax=max(rank)
#         r=min(rank)
#     else:
#         rmax=r=rank
# 
#     if kind.lower() in ['cano', 'canotensor']:
#         return CanoTensor(name=name, val=val, core=core,basis=basis,Fourier=Fourier,fft_form=fft_form).truncate(rank=r, tol=eps)
#     elif kind.lower() in ['tucker']:
#         return Tucker(name=name, val=val, core=core, basis=basis,Fourier=Fourier,fft_form=fft_form).truncate(rank=rank, tol=eps)
#     elif kind.lower() in ['tt', 'tensortrain']:
#         return TensorTrain(val=val, core=core, eps=eps, rmax=rmax, name=name,
#                            Fourier=Fourier, vectorObj=vectorObj,fft_form=fft_form)
#     else:
#         raise ValueError("Unexpected argument value: '" + kind +"'")


# Path: ffthompy/tensorsLowRank/projection.py
import numpy as np
from ffthompy.trigpol import Grid
from ffthompy.tensorsLowRank.objects import SparseTensor


def grad_tensor(N, Y, kind='TensorTrain'):
    assert(kind.lower() in ['cano','canotensor','tucker','tt','tensortrain'])

    dim=Y.size

    freq=Grid.get_xil(N, Y, fft_form='c')