# Path: ffthompy/tensors/objects.py
# class TensorFuns(Representation):
# 
#     def mean_index(self):
#         return mean_index(self.N, self.fft_form)
# 
#     def __getitem__(self, ii):
#         return self.val[ii]
# 
#     def pN(self):
#         return np.prod(self.N)
# 
#     def point(self, ii):
#         val=np.empty(self.shape)
#         for ind in np.ndindex(*self.shape):
#             val[ind]=self.val[ind][ii]
#         return val
# 
#     def sub(self, ii):
#         self.val[ii]
# 
#     def update(self, **kwargs):
#         return self.__dict__.update(**kwargs)
# 
#     def _copy(self, keys, **kwargs):
#         data={k:copy(self.__dict__[k]) for k in keys}
#         data.update(kwargs)
#         return self.__class__(**data)
# 
#     def copy(self, **kwargs):
#         return self._copy(self.keys, **kwargs)
# 
#     def _set_fft(self, fft_form):
#         assert(fft_form in ['c', 'r', 0])
# 
#         if fft_form in ['r']:
#             self.N_fft=self.get_N_real(self.N)
#             self.fftn=rfftn
#             self.ifftn=irfftn
#             self.fft_coef=np.prod(self.N)
#         elif fft_form in [0]:
#             self.N_fft=self.N
#             self.fftn=fftn
#             self.ifftn=ifftn
#             self.fft_coef=1.
#         elif fft_form in ['c']:
#             self.N_fft=self.N
#             self.fftn=fftnc
#             self.ifftn=icfftn
#             self.fft_coef=1.
# 
#         self.fft_form=fft_form
# 
#     def __repr__(self, full=False, detailed=False):
#         keys=['order', 'name', 'Y', 'shape', 'N', 'Fourier', 'fft_form', 'origin', 'norm']
#         ss=self._repr(keys)
#         skip=4*' '
#         if np.prod(np.array(self.shape))<=36 or detailed:
#             ss+='{0}norm component-wise =\n{1}\n'.format(skip, str(self.norm(componentwise=True)))
#             ss+='{0}mean = \n{1}\n'.format(skip, str(self.mean()))
#         if full:
#             ss+='{0}val = \n{1}'.format(skip, str(self.val))
#         return ss
# 
#     @staticmethod
#     def get_N_real(N):
#         N_rfft=np.copy(N)
#         N_rfft[-1]=int(np.fix(N[-1]/2)+1) # N[-1]//2+1
#         return tuple(N_rfft)
# 
#     @staticmethod
#     def get_N(N_rfft):
#         N=np.copy(N_rfft)
#         N[-1]=N_rfft[-1]*2-1
#         return tuple(N)
# 
# Path: ffthompy/tensorsLowRank/fft1.py
# def fft(x, N):
#     return npfft.fft(x, axis=1)/N # numpy.fft.fft
# 
# def ifft(x, N,real_output=False):
#     if real_output:
#         return npfft.ifft(x, axis=1).real*N # numpy.fft.fft
#     else:
#         return npfft.ifft(x, axis=1)*N # numpy.fft.fft
# 
# def fftc(x, N):
#     """
#     centered 1-dimensional FFT algorithm
#     """
#     return npfft.fftshift(npfft.fft(x, axis=1),axes=1)/N
# 
# def icfft(Fx, N,real_output=False):
#     """
#     centered 1-dimensional inverse FFT algorithm
#     """
#     if real_output:
#         return npfft.ifft(npfft.ifftshift(Fx, axes=1), axis=1).real*N
#     else:
#         return npfft.ifft(npfft.ifftshift(Fx, axes=1), axis=1)*N
# 
# def cfftc(x, N):
#     """
#     centered 1-dimensional FFT algorithm
#     """
#     return npfft.fftshift(npfft.fft(npfft.ifftshift(x, axes=1), axis=1), axes=1)/N
# 
# def icfftc(Fx, N,real_output=False):
#     """
#     centered 1-dimensional inverse FFT algorithm
#     """
#     if real_output:
#         return npfft.fftshift(npfft.ifft(npfft.ifftshift(Fx, axes=1), axis=1), axes=1).real*N
#     else:
#         return npfft.fftshift(npfft.ifft(npfft.ifftshift(Fx, axes=1), axis=1), axes=1)*N
# 
# def srfft(x, N):
#     return fftpack.rfft(x.real, axis=1)/N  # 1-D real fft from scipy.fftpack.rfft
# 
# def sirfft(x, N, real_output=True):
#     return fftpack.irfft(x.real, axis=1)*N  # 1-D real inverse fft from scipy.fftpack.irfft


import numpy as np
import itertools
from ffthompy.tensors.objects import TensorFuns
from copy import deepcopy as copy
from ffthompy.tensorsLowRank.fft1 import fft, ifft, fftc, icfft, cfftc, icfftc, srfft,sirfft

fft_form_default='sr' # scipy rfft

def multiply(A, B, *args, **kwargs):
    """element-wise (Hadamard) product of A and B"""
    dim=A.__len__()
    assert(dim==B.__len__())
    C=[]
    for ii in range(dim):
        shape=(A[ii].shape[0]*B[ii].shape[0], A[ii].shape[1])
        val=np.empty(shape)
        for iimn, (mm, nn) in enumerate(itertools.product(list(range(A[ii].shape[0])), list(range(B[ii].shape[0])))):
            val[iimn] = A[ii][mm]*B[ii][nn]
        C.append(val)
    return C



# Path: ffthompy/tensors/objects.py
# class TensorFuns(Representation):
# 
#     def mean_index(self):
#         return mean_index(self.N, self.fft_form)
# 
#     def __getitem__(self, ii):
#         return self.val[ii]
# 
#     def pN(self):
#         return np.prod(self.N)
# 
#     def point(self, ii):
#         val=np.empty(self.shape)
#         for ind in np.ndindex(*self.shape):
#             val[ind]=self.val[ind][ii]
#         return val
# 
#     def sub(self, ii):
#         self.val[ii]
# 
#     def update(self, **kwargs):
#         return self.__dict__.update(**kwargs)
# 
#     def _copy(self, keys, **kwargs):
#         data={k:copy(self.__dict__[k]) for k in keys}
#         data.update(kwargs)
#         return self.__class__(**data)
# 
#     def copy(self, **kwargs):
#         return self._copy(self.keys, **kwargs)
# 
#     def _set_fft(self, fft_form):
#         assert(fft_form in ['c', 'r', 0])
# 
#         if fft_form in ['r']:
#             self.N_fft=self.get_N_real(self.N)
#             self.fftn=rfftn
#             self.ifftn=irfftn
#             self.fft_coef=np.prod(self.N)
#         elif fft_form in [0]:
#             self.N_fft=self.N
#             self.fftn=fftn
#             self.ifftn=ifftn
#             self.fft_coef=1.
#         elif fft_form in ['c']:
#             self.N_fft=self.N
#             self.fftn=fftnc
#             self.ifftn=icfftn
#             self.fft_coef=1.
# 
#         self.fft_form=fft_form
# 
#     def __repr__(self, full=False, detailed=False):
#         keys=['order', 'name', 'Y', 'shape', 'N', 'Fourier', 'fft_form', 'origin', 'norm']
#         ss=self._repr(keys)
#         skip=4*' '
#         if np.prod(np.array(self.shape))<=36 or detailed:
#             ss+='{0}norm component-wise =\n{1}\n'.format(skip, str(self.norm(componentwise=True)))
#             ss+='{0}mean = \n{1}\n'.format(skip, str(self.mean()))
#         if full:
#             ss+='{0}val = \n{1}'.format(skip, str(self.val))
#         return ss
# 
#     @staticmethod
#     def get_N_real(N):
#         N_rfft=np.copy(N)
#         N_rfft[-1]=int(np.fix(N[-1]/2)+1) # N[-1]//2+1
#         return tuple(N_rfft)
# 
#     @staticmethod
#     def get_N(N_rfft):
#         N=np.copy(N_rfft)
#         N[-1]=N_rfft[-1]*2-1
#         return tuple(N)
# 
# Path: ffthompy/tensorsLowRank/fft1.py
# def fft(x, N):
#     return npfft.fft(x, axis=1)/N # numpy.fft.fft
# 
# def ifft(x, N,real_output=False):
#     if real_output:
#         return npfft.ifft(x, axis=1).real*N # numpy.fft.fft
#     else:
#         return npfft.ifft(x, axis=1)*N # numpy.fft.fft
# 
# def fftc(x, N):
#     """
#     centered 1-dimensional FFT algorithm
#     """
#     return npfft.fftshift(npfft.fft(x, axis=1),axes=1)/N
# 
# def icfft(Fx, N,real_output=False):
#     """
#     centered 1-dimensional inverse FFT algorithm
#     """
#     if real_output:
#         return npfft.ifft(npfft.ifftshift(Fx, axes=1), axis=1).real*N
#     else:
#         return npfft.ifft(npfft.ifftshift(Fx, axes=1), axis=1)*N
# 
# def cfftc(x, N):
#     """
#     centered 1-dimensional FFT algorithm
#     """
#     return npfft.fftshift(npfft.fft(npfft.ifftshift(x, axes=1), axis=1), axes=1)/N
# 
# def icfftc(Fx, N,real_output=False):
#     """
#     centered 1-dimensional inverse FFT algorithm
#     """
#     if real_output:
#         return npfft.fftshift(npfft.ifft(npfft.ifftshift(Fx, axes=1), axis=1), axes=1).real*N
#     else:
#         return npfft.fftshift(npfft.ifft(npfft.ifftshift(Fx, axes=1), axis=1), axes=1)*N
# 
# def srfft(x, N):
#     return fftpack.rfft(x.real, axis=1)/N  # 1-D real fft from scipy.fftpack.rfft
# 
# def sirfft(x, N, real_output=True):
#     return fftpack.irfft(x.real, axis=1)*N  # 1-D real inverse fft from scipy.fftpack.irfft


# Path: ffthompy/tensorsLowRank/objects/tensors.py
import numpy as np
import itertools
from ffthompy.tensors.objects import TensorFuns
from copy import deepcopy as copy
from ffthompy.tensorsLowRank.fft1 import fft, ifft, fftc, icfft, cfftc, icfftc, srfft,sirfft


fft_form_default='sr' # scipy rfft

def multiply(A, B, *args, **kwargs):
    """element-wise (Hadamard) product of A and B"""
    dim=A.__len__()
    assert(dim==B.__len__())
    C=[]
    for ii in range(dim):
        shape=(A[ii].shape[0]*B[ii].shape[0], A[ii].shape[1])
        val=np.empty(shape)
        for iimn, (mm, nn) in enumerate(itertools.product(list(range(A[ii].shape[0])), list(range(B[ii].shape[0])))):
            val[iimn] = A[ii][mm]*B[ii][nn]
        C.append(val)
    return C



class LowRankTensorFuns(TensorFuns):