# hyperlearn package

## Submodules

## hyperlearn.base module

`hyperlearn.base.``Numpy`(*args)

`hyperlearn.base.``T`(X)

`hyperlearn.base.``Tensor`(X)

`hyperlearn.base.``Tensors`(*args)

`hyperlearn.base.``USE_NUMBA` = True

  Type Checks Updated 27/8/2018 ————————————————————

`hyperlearn.base.``array`(X)

`hyperlearn.base.``cast`(X, dtype)

`hyperlearn.base.``check`(f)

`hyperlearn.base.``constant`(X)

`hyperlearn.base.``diag`(input, diagonal=0, out=None) → Tensor
  - If `input` is a vector (1-D tensor), then returns a 2-D square tensor with the elements of `input` as the diagonal.
  - If `input` is a matrix (2-D tensor), then returns a 1-D tensor with the diagonal elements of `input`.

  The argument `diagonal` controls which diagonal to consider:

  - If `diagonal` = 0, it is the main diagonal.
  - If `diagonal` > 0, it is above the main diagonal.
  - If `diagonal` < 0, it is below the main diagonal.
  Args:
    input (Tensor): the input tensor diagonal (int, optional): the diagonal to consider out (Tensor, optional): the output tensor

  See also

  `torch.diagonal()` always returns the diagonal of its input.

  `torch.diagflat()` always constructs a tensor with diagonal elements specified by the input.

  Examples:

  Get the square matrix where the input vector is the diagonal:

      >>> a = torch.randn(3)
      >>> a
      tensor([ 0.5950,-0.0872, 2.3298])
      >>> torch.diag(a)
      tensor([[ 0.5950, 0.0000, 0.0000],
              [ 0.0000,-0.0872, 0.0000],
              [ 0.0000, 0.0000, 2.3298]])
      >>> torch.diag(a, 1)
      tensor([[ 0.0000, 0.5950, 0.0000, 0.0000],
              [ 0.0000, 0.0000,-0.0872, 0.0000],
              [ 0.0000, 0.0000, 0.0000, 2.3298],
              [ 0.0000, 0.0000, 0.0000, 0.0000]])

  Get the k-th diagonal of a given matrix:

      >>> a = torch.randn(3, 3)
      >>> a
      tensor([[-0.4264, 0.0255,-0.1064],
              [ 0.8795,-0.2429, 0.1374],
              [ 0.1029,-0.6482,-1.6300]])
      >>> torch.diag(a, 0)
      tensor([-0.4264,-0.2429,-1.6300])
      >>> torch.diag(a, 1)
      tensor([ 0.0255, 0.1374])

`hyperlearn.base.``diagSum`(X, Y, transpose_a=False)

`hyperlearn.base.``dtype`(tensor)

`hyperlearn.base.``einsum`(notation, *args, tensor=False)

`hyperlearn.base.``eps`(X)

`hyperlearn.base.``isArray`(X)

`hyperlearn.base.``isDict`(X)

`hyperlearn.base.``isIterable`(X)

`hyperlearn.base.``isList`(X)

`hyperlearn.base.``isTensor`(X)

`hyperlearn.base.``ones`(*sizes, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor

  Returns a tensor filled with the scalar value 1, with the shape defined by the variable argument `sizes`.

  Args:
    sizes (int…): a sequence of integers defining the shape of the output tensor.
      Can be a variable number of arguments or a collection like a list or tuple.

    out (Tensor, optional): the output tensor dtype (`torch.dtype`, optional): the desired data type of returned tensor.

    > Default: if `None`, uses a global default (see `torch.set_default_tensor_type()`).

    layout (`torch.layout`, optional): the desired layout of returned Tensor.
      Default: `torch.strided`.
    device (`torch.device`, optional): the desired device of returned tensor.
      Default: if `None`, uses the current device for the default tensor type (see `torch.set_default_tensor_type()`). `device` will be the CPU for CPU tensor types and the current CUDA device for CUDA tensor types.
    requires_grad (bool, optional): If autograd should record operations on the
      returned tensor. Default: `False`.

  Example:

      >>> torch.ones(2, 3)
      tensor([[ 1.,  1.,  1.],
              [ 1.,  1.,  1.]])

      >>> torch.ones(5)
      tensor([ 1.,  1.,  1.,  1.,  1.])

`hyperlearn.base.``resolution`(X)

`hyperlearn.base.``return_numpy`(args)

`hyperlearn.base.``return_torch`(args)

`hyperlearn.base.``rowSum`(X, Y=None, transpose_a=False)

`hyperlearn.base.``squareSum`(X)

`hyperlearn.base.``stack`(*args)

`hyperlearn.base.``t_einsum`()

  einsum(equation, operands) -> Tensor

  This function provides a way of computing multilinear expressions (i.e. sums of products) using the Einstein summation convention.

  Args:
    equation (string): The equation is given in terms of lower case letters (indices) to be associated
      with each dimension of the operands and result. The left hand side lists the operands dimensions, separated by commas. There should be one index letter per tensor dimension. The right hand side follows after -> and gives the indices for the output. If the -> and right hand side are omitted, it implicitly defined as the alphabetically sorted list of all indices appearing exactly once in the left hand side. The indices not apprearing in the output are summed over after multiplying the operands entries. If an index appears several times for the same operand, a diagonal is taken. Ellipses … represent a fixed number of dimensions. If the right hand side is inferred, the ellipsis dimensions are at the beginning of the output.
    operands (list of Tensors): The operands to compute the Einstein sum of.
      Note that the operands are passed as a list, not as individual arguments.

  Examples:

      >>> x = torch.randn(5)
      >>> y = torch.randn(4)
      >>> torch.einsum('i,j->ij', (x,y))  # outer product
      tensor([[-0.0570, -0.0286, -0.0231,  0.0197],
              [ 1.2616,  0.6335,  0.5113, -0.4351],
              [ 1.4452,  0.7257,  0.5857, -0.4984],
              [-0.4647, -0.2333, -0.1883,  0.1603],
              [-1.1130, -0.5588, -0.4510,  0.3838]])

      >>> A = torch.randn(3,5,4)
      >>> l = torch.randn(2,5)
      >>> r = torch.randn(2,4)
      >>> torch.einsum('bn,anm,bm->ba', (l,A,r)) # compare torch.nn.functional.bilinear
      tensor([[-0.3430, -5.2405,  0.4494],
              [ 0.3311,  5.5201, -3.0356]])

      >>> As = torch.randn(3,2,5)
      >>> Bs = torch.randn(3,5,4)
      >>> torch.einsum('bij,bjk->bik', (As, Bs)) # batch matrix multiplication
      tensor([[[-1.0564, -1.5904,  3.2023,  3.1271],
               [-1.6706, -0.8097, -0.8025, -2.1183]],

              [[ 4.2239,  0.3107, -0.5756, -0.2354],
               [-1.4558, -0.3460,  1.5087, -0.8530]],

              [[ 2.8153,  1.8787, -4.3839, -1.2112],
               [ 0.3728, -2.1131,  0.0921,  0.8305]]])

      >>> A = torch.randn(3, 3)
      >>> torch.einsum('ii->i', (A,)) # diagonal
      tensor([-0.7825,  0.8291, -0.1936])

      >>> A = torch.randn(4, 3, 3)
      >>> torch.einsum('...ii->...i', (A,)) # batch diagonal
      tensor([[-1.0864,  0.7292,  0.0569],
              [-0.9725, -1.0270,  0.6493],
              [ 0.5832, -1.1716, -1.5084],
              [ 0.4041, -1.1690,  0.8570]])

      >>> A = torch.randn(2, 3, 4, 5)
      >>> torch.einsum('...ij->...ji', (A,)).shape # batch permute
      torch.Size([2, 3, 5, 4])

`hyperlearn.base.``torch_dot`()

  matmul(tensor1, tensor2, out=None) -> Tensor

  Matrix product of two tensors.

  The behavior depends on the dimensionality of the tensors as follows:

  - If both tensors are 1-dimensional, the dot product (scalar) is returned.
  - If both arguments are 2-dimensional, the matrix-matrix product is returned.
  - If the first argument is 1-dimensional and the second argument is 2-dimensional, a 1 is prepended to its dimension for the purpose of the matrix multiply. After the matrix multiply, the prepended dimension is removed.
  - If the first argument is 2-dimensional and the second argument is 1-dimensional, the matrix-vector product is returned.
  - If both arguments are at least 1-dimensional and at least one argument is N-dimensional (where N > 2), then a batched matrix multiply is returned. If the first argument is 1-dimensional, a 1 is prepended to its dimension for the purpose of the batched matrix multiply and removed after. If the second argument is 1-dimensional, a 1 is appended to its dimension for the purpose of the batched matrix multiple and removed after. The non-matrix (i.e. batch) dimensions are broadcasted (and thus must be broadcastable). For example, if `tensor1` is a \((j \times 1 \times n \times m)\) tensor and `tensor2` is a \((k \times m \times p)\) tensor, `out` will be an \((j \times k \times n \times p)\) tensor.

  Note

  The 1-dimensional dot product version of this function does not support an `out` parameter.

  Arguments:
    tensor1 (Tensor): the first tensor to be multiplied tensor2 (Tensor): the second tensor to be multiplied out (Tensor, optional): the output tensor

  Example:

      >>> # vector x vector
      >>> tensor1 = torch.randn(3)
      >>> tensor2 = torch.randn(3)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([])
      >>> # matrix x vector
      >>> tensor1 = torch.randn(3, 4)
      >>> tensor2 = torch.randn(4)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([3])
      >>> # batched matrix x broadcasted vector
      >>> tensor1 = torch.randn(10, 3, 4)
      >>> tensor2 = torch.randn(4)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([10, 3])
      >>> # batched matrix x batched matrix
      >>> tensor1 = torch.randn(10, 3, 4)
      >>> tensor2 = torch.randn(10, 4, 5)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([10, 3, 5])
      >>> # batched matrix x broadcasted matrix
      >>> tensor1 = torch.randn(10, 3, 4)
      >>> tensor2 = torch.randn(4, 5)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([10, 3, 5])

## hyperlearn.linalg module

`hyperlearn.linalg.``cholesky`(XTX, alpha=None, fast=True)

  Computes the Cholesky Decompsition of a Hermitian Matrix (Positive Symmetric Matrix) giving a Upper Triangular Matrix.

  Cholesky Decomposition is used as the default solver in HyperLearn, as it is super fast and allows regularization. HyperLearn’s implementation also handles rank deficient and ill-conditioned matrices perfectly with the help of the limiting behaivour of adding forced epsilon regularization.

  If USE_GPU:
    Uses PyTorch’s Cholesky. Speed is OK.
  If CPU:
    Uses Numpy’s Fortran C based Cholesky. If NUMBA is not installed, uses very fast LAPACK functions.

  Alpha is added for regularization purposes. This prevents system rounding errors and promises better convergence rates.

`hyperlearn.linalg.``invCholesky`(X, fast=False)

  Computes the Inverse of a Hermitian Matrix (Positive Symmetric Matrix) after provided with Cholesky’s Lower Triangular Matrix.

  This is used in conjunction in solveCholesky, where the inverse of the covariance matrix is needed.

  If USE_GPU:
    Uses PyTorch’s Triangular Solve given identity matrix. Speed is OK.
  If CPU:
    Uses very fast LAPACK algorithms for triangular system inverses.

  Note that LAPACK’s single precision (float32) solver (strtri) is much more unstable than double (float64). So, default strtri is OFF. However, speeds are reduced by 50%.

`hyperlearn.linalg.``pinvCholesky`(X, alpha=None, fast=False)

  Computes the approximate pseudoinverse of any matrix using Cholesky Decomposition This means X @ pinv(X) approx = eye(n).

  Note that this is super fast, and will be used in HyperLearn as the default pseudoinverse solver for any matrix. Care is taken to make the algorithm converge, and this is done via forced epsilon regularization.

  HyperLearn’s implementation also handles rank deficient and ill-conditioned matrices perfectly with the help of the limiting behaivour of adding forced epsilon regularization.

  If USE_GPU:
    Uses PyTorch’s Cholesky. Speed is OK.
  If CPU:
    Uses Numpy’s Fortran C based Cholesky. If NUMBA is not installed, uses very fast LAPACK functions.

  Alpha is added for regularization purposes. This prevents system rounding errors and promises better convergence rates.

`hyperlearn.linalg.``cholSolve`(A, b, alpha=None)

  [Added 20/10/2018] Faster than direct inverse solve. Finds coefficients in linear regression allowing A @ theta = b. Notice auto adds epsilon jitter if solver fails.

`hyperlearn.linalg.``svd`(X, fast=True, U_decision=False, transpose=True)

  [Edited 9/11/2018 –> Modern Big Data Algorithms p/n ratio check] Computes the Singular Value Decomposition of any matrix. So, X = U * S @ VT. Note will compute svd(X.T) if p > n. Should be 99% same result. This means this implementation’s time complexity is O[ min(np^2, n^2p) ]

  If USE_GPU:
    Uses PyTorch’s SVD. PyTorch uses (for now) a NON divide-n-conquer algo. Submitted report to PyTorch: https://github.com/pytorch/pytorch/issues/11174
  If CPU:
    Uses Numpy’s Fortran C based SVD. If NUMBA is not installed, uses divide-n-conqeur LAPACK functions.
  If Transpose:
    Will compute if possible svd(X.T) instead of svd(X) if p > n. Default setting is TRUE to maintain speed.

  SVD_Flip is used for deterministic output. Does NOT follow Sklearn convention. This flips the signs of U and VT, using VT_based decision.

`hyperlearn.linalg.``lu`(X, L_only=False, U_only=False)

  [Edited 8/11/2018 Changed to LAPACK LU if L/U only wanted] Computes the LU Decomposition of any matrix with pivoting. Provides L only or U only if specified.

  Much faster than Scipy if only U/L wanted, and more memory efficient, since data is altered inplace.

`hyperlearn.linalg.``qr`(X, Q_only=False, R_only=False, overwrite=False)

  [Edited 8/11/2018 Added Q, R only parameters. Faster than Numba] [Edited 9/11/2018 Made R only more memory efficient (no data copying)] Computes the reduced QR Decomposition of any matrix. Uses optimized NUMBA QR if avaliable else use’s Scipy’s version.

  Provides Q or R only if specified, and is must faster + more memory efficient since data is changed inplace.

`hyperlearn.linalg.``pinv`(X, alpha=None, fast=True)

  Computes the pseudoinverse of any matrix. This means X @ pinv(X) = eye(n).

  Optional alpha is used for regularization purposes.

  If USE_GPU:
    Uses PyTorch’s SVD. PyTorch uses (for now) a NON divide-n-conquer algo. Submitted report to PyTorch: https://github.com/pytorch/pytorch/issues/11174
  If CPU:
    Uses Numpy’s Fortran C based SVD. If NUMBA is not installed, uses divide-n-conqeur LAPACK functions.
  Condition number is:
    float32 = 1e3 * eps * max(S) float64 = 1e6 * eps * max(S)

`hyperlearn.linalg.``pinvh`(XTX, alpha=None, fast=True)

  Computes the pseudoinverse of a Hermitian Matrix (Positive Symmetric Matrix) using Eigendecomposition.

  Uses the fact that the matrix is special, and so time complexity is approximately reduced by 1/2 or more when compared to full SVD.

  If USE_GPU:
    Uses PyTorch’s EIGH. PyTorch uses (for now) a non divide-n-conquer algo.
  If CPU:
    Uses Numpy’s Fortran C based EIGH. If NUMBA is not installed, uses very fast divide-n-conqeur LAPACK functions. Note Scipy’s EIGH as of now is NON divide-n-conquer. Submitted report to Scipy: https://github.com/scipy/scipy/issues/9212
  Condition number is:
    float32 = 1e3 * eps * max(abs(S)) float64 = 1e6 * eps * max(abs(S))

  Alpha is added for regularization purposes. This prevents system rounding errors and promises better convergence rates.

`hyperlearn.linalg.``eigh`(XTX, alpha=None, fast=True, svd=False, positive=False, qr=False)

  Computes the Eigendecomposition of a Hermitian Matrix (Positive Symmetric Matrix).

  Note: Slips eigenvalues / eigenvectors with MAX first. Scipy convention is MIN first, but MAX first is SVD convention.

  Uses the fact that the matrix is special, and so time complexity is approximately reduced by 1/2 or more when compared to full SVD.

  If POSITIVE is True, then all negative eigenvalues will be set to zero, and return value will be VT and not V.

  If SVD is True, then eigenvalues will be square rooted as well.

  If USE_GPU:
    Uses PyTorch’s EIGH. PyTorch uses (for now) a non divide-n-conquer algo.
  If CPU:
    Uses Numpy’s Fortran C based EIGH. If NUMBA is not installed, uses very fast divide-n-conqeur LAPACK functions. Note Scipy’s EIGH as of now is NON divide-n-conquer. Submitted report to Scipy: https://github.com/scipy/scipy/issues/9212

  Alpha is added for regularization purposes. This prevents system rounding errors and promises better convergence rates.

  Also uses eig_flip to flip the signs of the eigenvectors to ensure deterministic output.

`hyperlearn.linalg.``pinvEig`(X, alpha=None, fast=True)

  Computes the approximate pseudoinverse of any matrix X using Eigendecomposition on the covariance matrix XTX or XXT

  Uses a special trick where:
    If n >= p: X^-1 approx = (XT @ X)^-1 @ XT If n < p: X^-1 approx = XT @ (X @ XT)^-1
  If USE_GPU:
    Uses PyTorch’s EIGH. PyTorch uses (for now) a non divide-n-conquer algo.
  If CPU:
    Uses Numpy’s Fortran C based EIGH. If NUMBA is not installed, uses very fast divide-n-conqeur LAPACK functions. Note Scipy’s EIGH as of now is NON divide-n-conquer. Submitted report to Scipy: https://github.com/scipy/scipy/issues/9212
  Condition number is:
    float32 = 1e3 * eps * max(abs(S)) float64 = 1e6 * eps * max(abs(S))

  Alpha is added for regularization purposes. This prevents system rounding errors and promises better convergence rates.

`hyperlearn.linalg.``eig`(X, alpha=None, fast=True, U_decision=False, svd=False, stable=False)

  [Edited 8/11/2018 Made QR-SVD even faster –> changed to n >= p from n >= 5/3p] Computes the Eigendecomposition of any matrix using either QR then SVD or just SVD. This produces much more stable solutions that pure eigh(covariance), and thus will be necessary in some cases.

  If STABLE is True, then EIGH will be bypassed, and instead SVD or QR/SVD will be used instead. This is to guarantee stability, since EIGH uses epsilon jitter along the diagonal of the covariance matrix.

  If n >= 5/3 * p:

    Uses QR followed by SVD noticing that U is not needed. This means Q @ U is not required, reducing work.

    Note Sklearn’s Incremental PCA was used for the constant 5/3 [Matrix Computations, Third Edition, G. Holub and C. Van Loan, Chapter 5, section 5.4.4, pp 252-253.]

  Else If n >= p:
    SVD is used, as QR would be slower.
  Else If n <= p:
    SVD Transpose is used svd(X.T)
  If stable is False:
    Eigh is used or SVD depending on the memory requirement.

  Eig is the most stable Eigendecomposition in HyperLearn. It surpasses the stability of Eigh, as no epsilon jitter is added, unless specified when stable = False.

## hyperlearn.utils module

class `hyperlearn.utils.``lapack`(function, fast=True, numba=None)

  Bases: `object`

  [Added 11/11/2018] [Edited 13/11/2018 -> made into a class] [Edited 14/11/2018 -> fixed class] Get a LAPACK function based on the dtype(X). Acts like Scipy.

`hyperlearn.utils.``svd_flip`(U, VT, U_decision=True)

  Flips the signs of U and VT for SVD in order to force deterministic output.

  Follows Sklearn convention by looking at U’s maximum in columns as default.

`hyperlearn.utils.``eig_flip`(V)

  Flips the signs of V for Eigendecomposition in order to force deterministic output.

  Follows Sklearn convention by looking at V’s maximum in columns as default. This essentially mirrors svd_flip(U_decision = False)

`hyperlearn.utils.``memoryXTX`(X)

  Computes the memory usage for X.T @ X so that error messages can be broadcast without submitting to a memory error.

`hyperlearn.utils.``memoryCovariance`(X)

  Computes the memory usage for X.T @ X or X @ X.T so that error messages can be broadcast without submitting to a memory error.

`hyperlearn.utils.``memorySVD`(X)

  Computes the approximate memory usage of SVD(X) [transpose or not]. How it’s computed:

  > X = U * S * VT U(n,p) * S(p) * VT(p,p) This means RAM usgae is np+p+p^2 approximately.

  ### TODO: Divide N Conquer SVD vs old SVD

`hyperlearn.utils.``traceXTX`

  [Edited 18/10/2018] One drawback of truncated algorithms is that they can’t output the correct variance explained ratios, since the full eigenvalue decomp needs to be done. However, using linear algebra, trace(XT*X) = sum(eigenvalues).

  So, this function outputs the trace(XT*X) efficiently without computing explicitly XT*X.

  Changes –> now uses Numba which is approx 20% faster.

`hyperlearn.utils.``fastDot`(A, B, C)

  [Added 23/9/2018] [Updated 1/10/2018 Error in calculating which is faster] Computes a fast matrix multiplication of 3 matrices. Either performs (A @ B) @ C or A @ (B @ C) depending which is more efficient.

`hyperlearn.utils.``rowSum`(X, norm=False)

  [Added 22/10/2018] Combines rowSum for matrices and arrays.

`hyperlearn.utils.``rowSum_A`

  [Added 22/10/2018] Computes rowSum**2 for dense array efficiently, instead of using einsum

`hyperlearn.utils.``reflect`(X, n_jobs=1)

  [Added 15/10/2018] [Edited 18/10/2018] Reflects lower triangular of matrix efficiently to upper. Notice much faster than say X += X.T or naive:

  > for i in range(n):
  >   for j in range(i, n):
  >     X[i,j] = X[j,i]

  In fact, it is much faster to perform vertically:
    for i in range(1, n):

      Xi = X[i] for j in range(i):

      > X[j,i] = Xi[j]

  The trick is to notice X[i], which reduces array access.

`hyperlearn.utils.``addDiagonal`(X, c=1)

  [Added 11/11/2018] Add c to diagonal of matrix

`hyperlearn.utils.``setDiagonal`(X, c=1)

  [Added 11/11/2018] Set c to diagonal of matrix

## hyperlearn.random module

`hyperlearn.random.``uniform`(left, right, n, p=None, dtype=<class 'numpy.float32'>)

  [Added 6/11/2018]

  Produces pseudo-random uniform numbers between left and right range. Notice much more memory efficient than Numpy, as provides a DTYPE argument (float32 supported).

`hyperlearn.random.``uniform_vector`

## hyperlearn.exceptions module

exception `hyperlearn.exceptions.``FutureExceedsMemory`(text='Operation done in the future uses more memory than what is free. HyperLearn')

  Bases: `BaseException`

exception `hyperlearn.exceptions.``PartialWrongShape`(text='Partial SVD or Eig needs the same number of columns in both the previous iteration and the future iteration. Currenlty, the number of columns is different.')

  Bases: `BaseException`

## hyperlearn.multiprocessing module

## hyperlearn.numba module

`hyperlearn.numba.``svd`

`hyperlearn.numba.``pinv`

`hyperlearn.numba.``eigh`

`hyperlearn.numba.``cholesky`

`hyperlearn.numba.``lstsq`

`hyperlearn.numba.``qr`

`hyperlearn.numba.``norm`

`hyperlearn.numba.``mean`(X, axis=0)

`hyperlearn.numba.``sign`

`hyperlearn.numba.``arange`

`hyperlearn.numba.``minimum`

`hyperlearn.numba.``maximum`

`hyperlearn.numba.``multsum`

`hyperlearn.numba.``squaresum`

## hyperlearn.solvers module

`hyperlearn.solvers.``solve`(X, y, tol=1e-06, condition_limit=100000000.0, alpha=None, weights=None, copy=False, non_negative=False, max_iter=None)

  [As of 12/9/2018, an optional non_negative argument is added. Note as accurate as Scipy’s NNLS, by copies ideas from gradient descent.]

  [NOTE: as of 12/9/2018, LSMR is default in HyperLearn, replacing the 2nd fastest Cholesky Solve. LSMR is 2-4 times faster, and uses N less memory]

      >>> WEIGHTS is an array of Weights for Weighted / Generalized Least Squares [default None]
      >>> theta = (XT*W*X)^-1*(XT*W*y)

  Implements extremely fast least squares LSMR using orthogonalization as seen in Scipy’s LSMR and https://arxiv.org/abs/1006.0758 [LSMR: An iterative algorithm for sparse least-squares problems] by David Fong, Michael Saunders.

  Scipy’s version of LSMR is surprisingly slow, as some slow design factors were used (ie np.sqrt(1 number) is slower than number**0.5, or min(a,b) is slower than using 1 if statement.)

  ALPHA is provided for regularization purposes like Ridge Regression.

  This algorithm works well for Sparse Matrices as well, and the time complexity analysis is approx:

    X.T @ y * min(n,p) times + 3 or so O(n) operations ==> O(np)*min(n,p) ==> either min(O(n^2p + n), O(np^2 + n)) *** Note if Weights is present, complexity increases.

    > Instead of fitting X^T*W*X, fits X^T/sqrt(W) So, O(np+n) is needed extra.

  This complexity is much better than Cholesky Solve which is the next fastest in HyperLearn. Cholesky requires O(np^2) for XT * X, then Cholesky needs an extra 1/3*O(np^2), then inversion takes another 1/3*(np^2), and finally (XT*y) needs O(np).

  So Cholesky needs O(5/3np^2 + np) >> min(O(n^2p + n), O(np^2 + n))

  So by factor analysis, expect LSMR to be approx 2 times faster or so.

  Interestingly, the Space Complexity is even more staggering. LSMR takes only maximum O(np^2) space for the computation of XT * y + some overhead.

  > *** Note if Weights is present, and COPY IS TRUE, then memory is DOUBLED.
  >   Hence, try setting COPY to FALSE, memory will not change, and X will return back to its original state afterwards.

  Cholesky requires XT * X space, which is already max O(n^2p) [which is huge]. Essentially, Cholesky shines when P is large, but N is small. LSMR is good for large N, medium P

  Theta_hat = (XT * W * X)^-1 * (XT * y) In other words in gradient descent / iterative solves solve:

  > X * sqrt(W) * theta_hat = y * sqrt(W)

  > or: X*sqrt(W) ==> y*sqrt(W)

`hyperlearn.solvers.``solveCholesky`(X, y, alpha=None, fast=True)
  [Added 23/9/2018 added matrix multiplication decisions (faster multiply)
    ie: if (XTX)^1(XTy) or ((XTX)^-1XT)y is faster]

  [Edited 20/10/2018 Major update - added LAPACK cholSolve –> 20% faster] [Edited 30/10/2018 Reduced RAM usage by clearing unused variables]

  Computes the Least Squares solution to X @ theta = y using Cholesky Decomposition. This is the default solver in HyperLearn.

  Cholesky Solving is used as the 2nd default solver [as of 12/9/2018, default has been switched to LSMR (called solve)] in HyperLearn, as it is super fast and allows regularization. HyperLearn’s implementation also handles rank deficient and ill-conditioned matrices perfectly with the help of the limiting behaivour of adding forced epsilon regularization.

  Optional alpha is used for regularization purposes.

  Method | Operations | Factor * np^2 |

  |-----------|—————–|---------------| | Cholesky | 1/3 * np^2 | 1/3 | | QR | p^3/3 + np^2 | 1 - p/3n | | SVD | p^3 + np^2 | 1 - p/n |

  If USE_GPU:
    Uses PyTorch’s Cholesky and Triangular Solve given identity matrix. Speed is OK.
  If CPU:
    Uses Numpy’s Fortran C based Cholesky. If NUMBA is not installed, uses very fast LAPACK functions. Also, uses very fast LAPACK algorithms for triangular system inverses.

  Note that LAPACK’s single precision (float32) solver (strtri) is much more unstable than double (float64). You might see stability problems if FAST = TRUE. Set it to FALSE if theres issues.

`hyperlearn.solvers.``solveSVD`(X, y, n_components=None, alpha=None, fast=True)

  [Edited 6/11/2018 Added n_components for Partial Solving] Computes the Least Squares solution to X @ theta = y using SVD. Slow, but most accurate. Specify n_components to reduce overfitting. Heurestic is 95% of variance is captured, if set to ‘auto’.

  Optional alpha is used for regularization purposes.

  If USE_GPU:
    Uses PyTorch’s SVD. PyTorch uses (for now) a NON divide-n-conquer algo. Submitted report to PyTorch: https://github.com/pytorch/pytorch/issues/11174
  If CPU:
    Uses Numpy’s Fortran C based SVD. If NUMBA is not installed, uses divide-n-conqeur LAPACK functions.
  Condition number is:
    float32 = 1e3 * eps * max(S) float64 = 1e6 * eps * max(S)

`hyperlearn.solvers.``solveEig`(X, y, alpha=None, fast=True)

  [Edited 30/10/2018 Reduced RAM usage by clearing unused variables]

  Computes the Least Squares solution to X @ theta = y using Eigendecomposition on the covariance matrix XTX or XXT. Medium speed and accurate, where this lies between SVD and Cholesky.

  Optional alpha is used for regularization purposes.

  If USE_GPU:
    Uses PyTorch’s EIGH. PyTorch uses (for now) a non divide-n-conquer algo. Submitted report to PyTorch: https://github.com/pytorch/pytorch/issues/11174
  If CPU:
    Uses Numpy’s Fortran C based EIGH. If NUMBA is not installed, uses divide-n-conqeur LAPACK functions. Note Scipy’s EIGH as of now is NON divide-n-conquer. Submitted report to Scipy: https://github.com/scipy/scipy/issues/9212
  Condition number is:
    float32 = 1e3 * eps * max(abs(S)) float64 = 1e6 * eps * max(abs(S))

  Alpha is added for regularization purposes. This prevents system rounding errors and promises better convergence rates.

`hyperlearn.solvers.``solvePartial`(X, y, n_components=None, alpha=None, fast=True)

  [Added 6/11/2018] Computes the Least Squares solution to X @ theta = y using Randomized SVD. Much faster than normal SVD solving, and is not prone is overfitting.

  Optional alpha is used for regularization purposes.

`hyperlearn.solvers.``lstsq`(X, y)

  Returns normal Least Squares solution using LAPACK and Numba if installed. PyTorch will default to Cholesky Solve.

`hyperlearn.solvers.``solveTLS`(X, y, solver='truncated')

  [Added 6/11/2018] Performs Total Least Squares based on the implementation in Wikipedia: https://en.wikipedia.org/wiki/Total_least_squares. The naming is rather deceptive, as it doesn’t mean it’ll yield better results than pure SVD solving. Normal linear regression assumes Y|X has gaussian noise. TLS assumes this AND X|Y has noise.

  Two solvers - full, truncated. Truncated is much much faster, as smallest eigen component is needed. Full solver uses Eigendecomposition, which is much much slower, but more accurate.

## hyperlearn.stats module

`hyperlearn.stats.``corr`(X, y)

`hyperlearn.stats.``qr_stats`(Q, R)

  XTX^-1 = RT * R

  h = diag Q * QT

  mean(h) used for normalized leverage

`hyperlearn.stats.``svd_stats`(U, S, VT)

  > 1

  XTX^-1 = V —– VT
    S^2

  h = diag U * UT

  mean(h) used for normalized leverage

`hyperlearn.stats.``ridge_stats`(U, S, VT, alpha=1)

  > S^2

  exp_theta_hat = diag V ——— VT

    S^2 + aI

    > S^2

  var_theta_hat = diag V ————- VT

    > (S^2 + aI)^2

    1

  XTX^-1 = V ——— VT

    S^2 + aI

    > S^2

  h = diag U ——— UT
    S^2 + aI

  mean(h) used for normalized leverage

## hyperlearn.big_data.base module

## hyperlearn.big_data.incremental module

`hyperlearn.big_data.incremental.``partialEig`(batch, S2, V, ratio=1, solver='full', tol=None, max_iter='auto')

  Fits a partial Eigendecomp after given old eigenvalues S2 and old eigenvector components V.

  Note that V will be used as the number of old components, so when calling truncated or randomized, will output a specific number of eigenvectors and eigenvalues.

  Checks if new batch’s size matches that of the old V.

  Note that PartialEig has different solvers. Either choose:
    1. full
         Solves full Eigendecompsition on the data. This is the most stable and will guarantee the most robust results. You can select the number of components to keep within the model later.
    2. truncated
         This keeps the top K right eigenvectors and top k eigenvalues, as determined by n_components. Note full Eig is not called for the truncated case, but rather ARPACK is called.
    3. randomized
         Same as truncated, but instead of using ARPACK, uses randomized Eig.

`hyperlearn.big_data.incremental.``partialSVD`(batch, S, VT, ratio=1, solver='full', tol=None, max_iter='auto')

  Fits a partial SVD after given old singular values S and old components VT.

  Note that VT will be used as the number of old components, so when calling truncated or randomized, will output a specific number of eigenvectors and singular values.

  Checks if new batch’s size matches that of the old VT.

  Note that PartialSVD has different solvers. Either choose:
    1. full
         Solves full SVD on the data. This is the most stable and will guarantee the most robust results. You can select the number of components to keep within the model later.
    2. truncated
         This keeps the top K right eigenvectors and top k right singular values, as determined by n_components. Note full SVD is not called for the truncated case, but rather ARPACK is called.
    3. randomized
         Same as truncated, but instead of using ARPACK, uses randomized SVD.

  Notice how Batch = U @ S @ VT. However, partialSVD returns S, VT, and not U. In order to get U, you might consider using the relation that X = U @ S @ VT, and approximating U by:

  > X = U @ S @ VT X @ V = U @ S (X @ V)/S = U

  > So, U = (X @ V)/S, so you can output U from (X @ V)/S

  > You can also get U partially and slowly using reverseU.

## hyperlearn.big_data.lsmr module

`hyperlearn.big_data.lsmr.``Orthogonalize`(a, b)

`hyperlearn.big_data.lsmr.``floatType`(dtype)

`hyperlearn.big_data.lsmr.``lsmr`(X, y, tol=1e-06, condition_limit=100000000.0, alpha=0, threshold=1000000000000.0, non_negative=False, max_iter=None)

  [As of 12/9/2018, an optional non_negative argument is added. Note as accurate as Scipy’s NNLS, by copies ideas from gradient descent.]

  Implements extremely fast least squares LSMR using orthogonalization as seen in Scipy’s LSMR and https://arxiv.org/abs/1006.0758 [LSMR: An iterative algorithm for sparse least-squares problems] by David Fong, Michael Saunders.

  Scipy’s version of LSMR is surprisingly slow, as some slow design factors were used (ie np.sqrt(1 number) is slower than number**0.5, or min(a,b) is slower than using 1 if statement.)

  ALPHA is provided for regularization purposes like Ridge Regression.

  This algorithm works well for Sparse Matrices as well, and the time complexity analysis is approx:
    X.T @ y * min(n,p) times + 3 or so O(n) operations ==> O(np)*min(n,p) ==> either min(O(n^2p + n), O(np^2 + n))

  This complexity is much better than Cholesky Solve which is the next fastest in HyperLearn. Cholesky requires O(np^2) for XT * X, then Cholesky needs an extra 1/3*O(np^2), then inversion takes another 1/3*(np^2), and finally (XT*y) needs O(np).

  So Cholesky needs O(5/3np^2 + np) >> min(O(n^2p + n), O(np^2 + n))

  So by factor analysis, expect LSMR to be approx 2 times faster or so. Interestingly, the Space Complexity is even more staggering. LSMR takes only maximum O(np^2) space for the computation of XT * y + some overhead.

  Cholesky requires XT * X space, which is already max O(n^2p) [which is huge]. Essentially, Cholesky shines when P is large, but N is small. LSMR is good for large N, medium P

## hyperlearn.big_data.randomized module

`hyperlearn.big_data.randomized.``randomizedEig`(X, n_components=2, max_iter='auto', solver='lu', n_oversamples=10)

  [Edited 9/11/2018 Fixed Eig_Flip] HyperLearn’s Randomized Eigendecomposition is an extension of Sklearn’s randomized SVD. HyperLearn notices that the computation of U is not necessary, hence will use QR followed by SVD or just SVD depending on the situation.

  Likewise, solver = LU is default, and follows randomizedSVD

  References

  - Sklearn’s RandomizedSVD
  - Finding structure with randomness: Stochastic algorithms for constructing approximate matrix decompositions Halko, et al., 2009 http://arxiv.org/abs/arXiv:0909.4061
  - A randomized algorithm for the decomposition of matrices Per-Gunnar Martinsson, Vladimir Rokhlin and Mark Tygert
  - An implementation of a randomized algorithm for principal component analysis A. Szlam et al. 2014

`hyperlearn.big_data.randomized.``randomizedPinv`(X, n_components=None, alpha=None)

  [Added 6/11/2018] Implements fast randomized pseudoinverse with regularization. Can be used as an approximation to the matrix inverse.

`hyperlearn.big_data.randomized.``randomizedSVD`(X, n_components=2, max_iter='auto', solver='lu', n_oversamples=10)

  [Edited 9/11/2018 Fixed SVD_flip] HyperLearn’s Fast Randomized SVD is approx 10 - 30 % faster than Sklearn’s implementation depending on n_components and max_iter.

  Uses NUMBA Jit accelerated functions when available, and tries to reduce memory overhead by chaining operations.

  Uses QR, LU or no solver to find the best SVD decomp. QR is most stable, but can be 2x slower than LU.

  ****n_oversamples = 10. This follows Sklearn convention to increase the chance
    of more accurate SVD.

  References

  - Sklearn’s RandomizedSVD
  - Finding structure with randomness: Stochastic algorithms for constructing approximate matrix decompositions Halko, et al., 2009 http://arxiv.org/abs/arXiv:0909.4061
  - A randomized algorithm for the decomposition of matrices Per-Gunnar Martinsson, Vladimir Rokhlin and Mark Tygert
  - An implementation of a randomized algorithm for principal component analysis A. Szlam et al. 2014

`hyperlearn.big_data.randomized.``randomized_projection`(X, k, solver='lu', max_iter=4)

  [Edited 8/11/2018 Added QR Q_only parameter] Projects X onto some random eigenvectors, then using a special variant of Orthogonal Iteration, finds the closest orthogonal representation for X.

  Solver can be QR or LU or None.

## hyperlearn.big_data.truncated module

`hyperlearn.big_data.truncated.``truncatedEigh`(XTX, n_components=2, tol=None, svd=False, which='largest')

  [Edited 6/11/2018 Added smallest / largest command] Computes the Truncated Eigendecomposition of a Hermitian Matrix (positive definite). K = 2 for default. Return format is LARGEST eigenvalue first.

  If SVD is True, then outputs S2**0.5 and sets negative S2 to 0 and outputs VT and not V.

  Uses ARPACK from Scipy to compute the truncated decomp. Note that to make it slightly more stable and faster, follows Sklearn’s random intialization from -1 -> 1.

  Also note tolerance is resolution(X), and NOT eps(X)

  Might switch to SPECTRA in the future.

  EIGH FLIP is called to flip the eigenvector signs for deterministic output.

`hyperlearn.big_data.truncated.``truncatedSVD`(X, n_components=2, tol=None, transpose=True, U_decision=False, which='largest')

  [Edited 6/11/2018 Added which command - can get largest or smallest eigen components] Computes the Truncated SVD of any matrix. K = 2 for default. Return format is LARGEST singular first first.

  Uses ARPACK from Scipy to compute the truncated decomp. Note that to make it slightly more stable and faster, follows Sklearn’s random intialization from -1 -> 1.

  Also note tolerance is resolution(X), and NOT eps(X). Also note TRANSPOSE is True. This means instead of computing svd(X) if p > n, then computing svd(X.T) is faster, but you must output VT.T, S, U.T

  Might switch to SPECTRA in the future.

  SVD FLIP is called to flip the VT signs for deterministic output. Note uses VT based decision and not U based decision. U_decision can be changed to TRUE for Sklearn convention

`hyperlearn.big_data.truncated.``truncatedEig`(X, n_components=2, tol=None, svd=False, which='largest')

  [Added 6/11/2018] Computes truncated eigendecomposition given any matrix X. Directly uses TruncatedSVD if memory is not enough, and returns eigen vectors/values. Also argument for smallest eigen components are provided.

## hyperlearn.decomposition.base module

## hyperlearn.decomposition.NMF module

## hyperlearn.decomposition.PCA module

class `hyperlearn.decomposition.PCA.``PCA`(n_components=2, solver='eig', alpha=None, fast=True, centre=True)

  Bases: `hyperlearn.decomposition.base._basePCA`

  Principal Component Analysis reduces the dimensionality of data by selecting the linear combinations of columns or features which maximises the total variance.

  Note, SKLEARN’s implementation is wasteful, as it uses a full SVD solver, which is slow and painful.

  HyperLearn’s implementation uses regularized Eigenvalue Decomposition, but if solver = ‘svd’, then full SVD is used.

  If USE_GPU:
    Uses PyTorch’s SVD (which is slow sadly), or EIGH. Speed is OK.
  If CPU:
    Uses Numpy’s Fortran C based SVD or EIGH. If NUMBA is not installed, uses very fast LAPACK functions.

  Alpha is added for regularization purposes. This prevents system rounding errors and promises better convergence rates.

  Note svd_flip is NOT same as SKLEARN, hence output may have reversed signs. V based decision is used as EIGH is faster, and U is not computed.

`hyperlearn.decomposition.PCA.``diag`(input, diagonal=0, out=None) → Tensor
  - If `input` is a vector (1-D tensor), then returns a 2-D square tensor with the elements of `input` as the diagonal.
  - If `input` is a matrix (2-D tensor), then returns a 1-D tensor with the diagonal elements of `input`.

  The argument `diagonal` controls which diagonal to consider:

  - If `diagonal` = 0, it is the main diagonal.
  - If `diagonal` > 0, it is above the main diagonal.
  - If `diagonal` < 0, it is below the main diagonal.
  Args:
    input (Tensor): the input tensor diagonal (int, optional): the diagonal to consider out (Tensor, optional): the output tensor

  See also

  `torch.diagonal()` always returns the diagonal of its input.

  `torch.diagflat()` always constructs a tensor with diagonal elements specified by the input.

  Examples:

  Get the square matrix where the input vector is the diagonal:

      >>> a = torch.randn(3)
      >>> a
      tensor([ 0.5950,-0.0872, 2.3298])
      >>> torch.diag(a)
      tensor([[ 0.5950, 0.0000, 0.0000],
              [ 0.0000,-0.0872, 0.0000],
              [ 0.0000, 0.0000, 2.3298]])
      >>> torch.diag(a, 1)
      tensor([[ 0.0000, 0.5950, 0.0000, 0.0000],
              [ 0.0000, 0.0000,-0.0872, 0.0000],
              [ 0.0000, 0.0000, 0.0000, 2.3298],
              [ 0.0000, 0.0000, 0.0000, 0.0000]])

  Get the k-th diagonal of a given matrix:

      >>> a = torch.randn(3, 3)
      >>> a
      tensor([[-0.4264, 0.0255,-0.1064],
              [ 0.8795,-0.2429, 0.1374],
              [ 0.1029,-0.6482,-1.6300]])
      >>> torch.diag(a, 0)
      tensor([-0.4264,-0.2429,-1.6300])
      >>> torch.diag(a, 1)
      tensor([ 0.0255, 0.1374])

`hyperlearn.decomposition.PCA.``ones`(*sizes, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor

  Returns a tensor filled with the scalar value 1, with the shape defined by the variable argument `sizes`.

  Args:
    sizes (int…): a sequence of integers defining the shape of the output tensor.
      Can be a variable number of arguments or a collection like a list or tuple.

    out (Tensor, optional): the output tensor dtype (`torch.dtype`, optional): the desired data type of returned tensor.

    > Default: if `None`, uses a global default (see `torch.set_default_tensor_type()`).

    layout (`torch.layout`, optional): the desired layout of returned Tensor.
      Default: `torch.strided`.
    device (`torch.device`, optional): the desired device of returned tensor.
      Default: if `None`, uses the current device for the default tensor type (see `torch.set_default_tensor_type()`). `device` will be the CPU for CPU tensor types and the current CUDA device for CUDA tensor types.
    requires_grad (bool, optional): If autograd should record operations on the
      returned tensor. Default: `False`.

  Example:

      >>> torch.ones(2, 3)
      tensor([[ 1.,  1.,  1.],
              [ 1.,  1.,  1.]])

      >>> torch.ones(5)
      tensor([ 1.,  1.,  1.,  1.,  1.])

`hyperlearn.decomposition.PCA.``t_einsum`()

  einsum(equation, operands) -> Tensor

  This function provides a way of computing multilinear expressions (i.e. sums of products) using the Einstein summation convention.

  Args:
    equation (string): The equation is given in terms of lower case letters (indices) to be associated
      with each dimension of the operands and result. The left hand side lists the operands dimensions, separated by commas. There should be one index letter per tensor dimension. The right hand side follows after -> and gives the indices for the output. If the -> and right hand side are omitted, it implicitly defined as the alphabetically sorted list of all indices appearing exactly once in the left hand side. The indices not apprearing in the output are summed over after multiplying the operands entries. If an index appears several times for the same operand, a diagonal is taken. Ellipses … represent a fixed number of dimensions. If the right hand side is inferred, the ellipsis dimensions are at the beginning of the output.
    operands (list of Tensors): The operands to compute the Einstein sum of.
      Note that the operands are passed as a list, not as individual arguments.

  Examples:

      >>> x = torch.randn(5)
      >>> y = torch.randn(4)
      >>> torch.einsum('i,j->ij', (x,y))  # outer product
      tensor([[-0.0570, -0.0286, -0.0231,  0.0197],
              [ 1.2616,  0.6335,  0.5113, -0.4351],
              [ 1.4452,  0.7257,  0.5857, -0.4984],
              [-0.4647, -0.2333, -0.1883,  0.1603],
              [-1.1130, -0.5588, -0.4510,  0.3838]])

      >>> A = torch.randn(3,5,4)
      >>> l = torch.randn(2,5)
      >>> r = torch.randn(2,4)
      >>> torch.einsum('bn,anm,bm->ba', (l,A,r)) # compare torch.nn.functional.bilinear
      tensor([[-0.3430, -5.2405,  0.4494],
              [ 0.3311,  5.5201, -3.0356]])

      >>> As = torch.randn(3,2,5)
      >>> Bs = torch.randn(3,5,4)
      >>> torch.einsum('bij,bjk->bik', (As, Bs)) # batch matrix multiplication
      tensor([[[-1.0564, -1.5904,  3.2023,  3.1271],
               [-1.6706, -0.8097, -0.8025, -2.1183]],

              [[ 4.2239,  0.3107, -0.5756, -0.2354],
               [-1.4558, -0.3460,  1.5087, -0.8530]],

              [[ 2.8153,  1.8787, -4.3839, -1.2112],
               [ 0.3728, -2.1131,  0.0921,  0.8305]]])

      >>> A = torch.randn(3, 3)
      >>> torch.einsum('ii->i', (A,)) # diagonal
      tensor([-0.7825,  0.8291, -0.1936])

      >>> A = torch.randn(4, 3, 3)
      >>> torch.einsum('...ii->...i', (A,)) # batch diagonal
      tensor([[-1.0864,  0.7292,  0.0569],
              [-0.9725, -1.0270,  0.6493],
              [ 0.5832, -1.1716, -1.5084],
              [ 0.4041, -1.1690,  0.8570]])

      >>> A = torch.randn(2, 3, 4, 5)
      >>> torch.einsum('...ij->...ji', (A,)).shape # batch permute
      torch.Size([2, 3, 5, 4])

`hyperlearn.decomposition.PCA.``torch_dot`()

  matmul(tensor1, tensor2, out=None) -> Tensor

  Matrix product of two tensors.

  The behavior depends on the dimensionality of the tensors as follows:

  - If both tensors are 1-dimensional, the dot product (scalar) is returned.
  - If both arguments are 2-dimensional, the matrix-matrix product is returned.
  - If the first argument is 1-dimensional and the second argument is 2-dimensional, a 1 is prepended to its dimension for the purpose of the matrix multiply. After the matrix multiply, the prepended dimension is removed.
  - If the first argument is 2-dimensional and the second argument is 1-dimensional, the matrix-vector product is returned.
  - If both arguments are at least 1-dimensional and at least one argument is N-dimensional (where N > 2), then a batched matrix multiply is returned. If the first argument is 1-dimensional, a 1 is prepended to its dimension for the purpose of the batched matrix multiply and removed after. If the second argument is 1-dimensional, a 1 is appended to its dimension for the purpose of the batched matrix multiple and removed after. The non-matrix (i.e. batch) dimensions are broadcasted (and thus must be broadcastable). For example, if `tensor1` is a \((j \times 1 \times n \times m)\) tensor and `tensor2` is a \((k \times m \times p)\) tensor, `out` will be an \((j \times k \times n \times p)\) tensor.

  Note

  The 1-dimensional dot product version of this function does not support an `out` parameter.

  Arguments:
    tensor1 (Tensor): the first tensor to be multiplied tensor2 (Tensor): the second tensor to be multiplied out (Tensor, optional): the output tensor

  Example:

      >>> # vector x vector
      >>> tensor1 = torch.randn(3)
      >>> tensor2 = torch.randn(3)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([])
      >>> # matrix x vector
      >>> tensor1 = torch.randn(3, 4)
      >>> tensor2 = torch.randn(4)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([3])
      >>> # batched matrix x broadcasted vector
      >>> tensor1 = torch.randn(10, 3, 4)
      >>> tensor2 = torch.randn(4)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([10, 3])
      >>> # batched matrix x batched matrix
      >>> tensor1 = torch.randn(10, 3, 4)
      >>> tensor2 = torch.randn(10, 4, 5)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([10, 3, 5])
      >>> # batched matrix x broadcasted matrix
      >>> tensor1 = torch.randn(10, 3, 4)
      >>> tensor2 = torch.randn(4, 5)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([10, 3, 5])

## hyperlearn.decomposition.PCA module

class `hyperlearn.decomposition.PCA.``PCA`(n_components=2, solver='eig', alpha=None, fast=True, centre=True)

  Bases: `hyperlearn.decomposition.base._basePCA`

  Principal Component Analysis reduces the dimensionality of data by selecting the linear combinations of columns or features which maximises the total variance.

  Note, SKLEARN’s implementation is wasteful, as it uses a full SVD solver, which is slow and painful.

  HyperLearn’s implementation uses regularized Eigenvalue Decomposition, but if solver = ‘svd’, then full SVD is used.

  If USE_GPU:
    Uses PyTorch’s SVD (which is slow sadly), or EIGH. Speed is OK.
  If CPU:
    Uses Numpy’s Fortran C based SVD or EIGH. If NUMBA is not installed, uses very fast LAPACK functions.

  Alpha is added for regularization purposes. This prevents system rounding errors and promises better convergence rates.

  Note svd_flip is NOT same as SKLEARN, hence output may have reversed signs. V based decision is used as EIGH is faster, and U is not computed.

`hyperlearn.decomposition.PCA.``diag`(input, diagonal=0, out=None) → Tensor
  - If `input` is a vector (1-D tensor), then returns a 2-D square tensor with the elements of `input` as the diagonal.
  - If `input` is a matrix (2-D tensor), then returns a 1-D tensor with the diagonal elements of `input`.

  The argument `diagonal` controls which diagonal to consider:

  - If `diagonal` = 0, it is the main diagonal.
  - If `diagonal` > 0, it is above the main diagonal.
  - If `diagonal` < 0, it is below the main diagonal.
  Args:
    input (Tensor): the input tensor diagonal (int, optional): the diagonal to consider out (Tensor, optional): the output tensor

  See also

  `torch.diagonal()` always returns the diagonal of its input.

  `torch.diagflat()` always constructs a tensor with diagonal elements specified by the input.

  Examples:

  Get the square matrix where the input vector is the diagonal:

      >>> a = torch.randn(3)
      >>> a
      tensor([ 0.5950,-0.0872, 2.3298])
      >>> torch.diag(a)
      tensor([[ 0.5950, 0.0000, 0.0000],
              [ 0.0000,-0.0872, 0.0000],
              [ 0.0000, 0.0000, 2.3298]])
      >>> torch.diag(a, 1)
      tensor([[ 0.0000, 0.5950, 0.0000, 0.0000],
              [ 0.0000, 0.0000,-0.0872, 0.0000],
              [ 0.0000, 0.0000, 0.0000, 2.3298],
              [ 0.0000, 0.0000, 0.0000, 0.0000]])

  Get the k-th diagonal of a given matrix:

      >>> a = torch.randn(3, 3)
      >>> a
      tensor([[-0.4264, 0.0255,-0.1064],
              [ 0.8795,-0.2429, 0.1374],
              [ 0.1029,-0.6482,-1.6300]])
      >>> torch.diag(a, 0)
      tensor([-0.4264,-0.2429,-1.6300])
      >>> torch.diag(a, 1)
      tensor([ 0.0255, 0.1374])

`hyperlearn.decomposition.PCA.``ones`(*sizes, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor

  Returns a tensor filled with the scalar value 1, with the shape defined by the variable argument `sizes`.

  Args:
    sizes (int…): a sequence of integers defining the shape of the output tensor.
      Can be a variable number of arguments or a collection like a list or tuple.

    out (Tensor, optional): the output tensor dtype (`torch.dtype`, optional): the desired data type of returned tensor.

    > Default: if `None`, uses a global default (see `torch.set_default_tensor_type()`).

    layout (`torch.layout`, optional): the desired layout of returned Tensor.
      Default: `torch.strided`.
    device (`torch.device`, optional): the desired device of returned tensor.
      Default: if `None`, uses the current device for the default tensor type (see `torch.set_default_tensor_type()`). `device` will be the CPU for CPU tensor types and the current CUDA device for CUDA tensor types.
    requires_grad (bool, optional): If autograd should record operations on the
      returned tensor. Default: `False`.

  Example:

      >>> torch.ones(2, 3)
      tensor([[ 1.,  1.,  1.],
              [ 1.,  1.,  1.]])

      >>> torch.ones(5)
      tensor([ 1.,  1.,  1.,  1.,  1.])

`hyperlearn.decomposition.PCA.``t_einsum`()

  einsum(equation, operands) -> Tensor

  This function provides a way of computing multilinear expressions (i.e. sums of products) using the Einstein summation convention.

  Args:
    equation (string): The equation is given in terms of lower case letters (indices) to be associated
      with each dimension of the operands and result. The left hand side lists the operands dimensions, separated by commas. There should be one index letter per tensor dimension. The right hand side follows after -> and gives the indices for the output. If the -> and right hand side are omitted, it implicitly defined as the alphabetically sorted list of all indices appearing exactly once in the left hand side. The indices not apprearing in the output are summed over after multiplying the operands entries. If an index appears several times for the same operand, a diagonal is taken. Ellipses … represent a fixed number of dimensions. If the right hand side is inferred, the ellipsis dimensions are at the beginning of the output.
    operands (list of Tensors): The operands to compute the Einstein sum of.
      Note that the operands are passed as a list, not as individual arguments.

  Examples:

      >>> x = torch.randn(5)
      >>> y = torch.randn(4)
      >>> torch.einsum('i,j->ij', (x,y))  # outer product
      tensor([[-0.0570, -0.0286, -0.0231,  0.0197],
              [ 1.2616,  0.6335,  0.5113, -0.4351],
              [ 1.4452,  0.7257,  0.5857, -0.4984],
              [-0.4647, -0.2333, -0.1883,  0.1603],
              [-1.1130, -0.5588, -0.4510,  0.3838]])

      >>> A = torch.randn(3,5,4)
      >>> l = torch.randn(2,5)
      >>> r = torch.randn(2,4)
      >>> torch.einsum('bn,anm,bm->ba', (l,A,r)) # compare torch.nn.functional.bilinear
      tensor([[-0.3430, -5.2405,  0.4494],
              [ 0.3311,  5.5201, -3.0356]])

      >>> As = torch.randn(3,2,5)
      >>> Bs = torch.randn(3,5,4)
      >>> torch.einsum('bij,bjk->bik', (As, Bs)) # batch matrix multiplication
      tensor([[[-1.0564, -1.5904,  3.2023,  3.1271],
               [-1.6706, -0.8097, -0.8025, -2.1183]],

              [[ 4.2239,  0.3107, -0.5756, -0.2354],
               [-1.4558, -0.3460,  1.5087, -0.8530]],

              [[ 2.8153,  1.8787, -4.3839, -1.2112],
               [ 0.3728, -2.1131,  0.0921,  0.8305]]])

      >>> A = torch.randn(3, 3)
      >>> torch.einsum('ii->i', (A,)) # diagonal
      tensor([-0.7825,  0.8291, -0.1936])

      >>> A = torch.randn(4, 3, 3)
      >>> torch.einsum('...ii->...i', (A,)) # batch diagonal
      tensor([[-1.0864,  0.7292,  0.0569],
              [-0.9725, -1.0270,  0.6493],
              [ 0.5832, -1.1716, -1.5084],
              [ 0.4041, -1.1690,  0.8570]])

      >>> A = torch.randn(2, 3, 4, 5)
      >>> torch.einsum('...ij->...ji', (A,)).shape # batch permute
      torch.Size([2, 3, 5, 4])

`hyperlearn.decomposition.PCA.``torch_dot`()

  matmul(tensor1, tensor2, out=None) -> Tensor

  Matrix product of two tensors.

  The behavior depends on the dimensionality of the tensors as follows:

  - If both tensors are 1-dimensional, the dot product (scalar) is returned.
  - If both arguments are 2-dimensional, the matrix-matrix product is returned.
  - If the first argument is 1-dimensional and the second argument is 2-dimensional, a 1 is prepended to its dimension for the purpose of the matrix multiply. After the matrix multiply, the prepended dimension is removed.
  - If the first argument is 2-dimensional and the second argument is 1-dimensional, the matrix-vector product is returned.
  - If both arguments are at least 1-dimensional and at least one argument is N-dimensional (where N > 2), then a batched matrix multiply is returned. If the first argument is 1-dimensional, a 1 is prepended to its dimension for the purpose of the batched matrix multiply and removed after. If the second argument is 1-dimensional, a 1 is appended to its dimension for the purpose of the batched matrix multiple and removed after. The non-matrix (i.e. batch) dimensions are broadcasted (and thus must be broadcastable). For example, if `tensor1` is a \((j \times 1 \times n \times m)\) tensor and `tensor2` is a \((k \times m \times p)\) tensor, `out` will be an \((j \times k \times n \times p)\) tensor.

  Note

  The 1-dimensional dot product version of this function does not support an `out` parameter.

  Arguments:
    tensor1 (Tensor): the first tensor to be multiplied tensor2 (Tensor): the second tensor to be multiplied out (Tensor, optional): the output tensor

  Example:

      >>> # vector x vector
      >>> tensor1 = torch.randn(3)
      >>> tensor2 = torch.randn(3)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([])
      >>> # matrix x vector
      >>> tensor1 = torch.randn(3, 4)
      >>> tensor2 = torch.randn(4)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([3])
      >>> # batched matrix x broadcasted vector
      >>> tensor1 = torch.randn(10, 3, 4)
      >>> tensor2 = torch.randn(4)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([10, 3])
      >>> # batched matrix x batched matrix
      >>> tensor1 = torch.randn(10, 3, 4)
      >>> tensor2 = torch.randn(10, 4, 5)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([10, 3, 5])
      >>> # batched matrix x broadcasted matrix
      >>> tensor1 = torch.randn(10, 3, 4)
      >>> tensor2 = torch.randn(4, 5)
      >>> torch.matmul(tensor1, tensor2).size()
      torch.Size([10, 3, 5])

## hyperlearn.discriminant_analysis.base module

## hyperlearn.discriminant_analysis.QDA module

## hyperlearn.impute.SVDImpute module

`hyperlearn.impute.SVDImpute.``fit`(X, n_components='auto', standardise=True, copy=True)

  [Added 31/10/2018] [Edited 2/11/2018 Fixed SVDImpute]

  Fits a SVD onto the training data by projecting it to a lower space after being intially filled with column means. By default, n_components is determined automatically using log(p+1). Setting too low or too high mirrors mean imputation, and deletes the purpose of SVD imputation.

  Returns: 1. S singular values 2. VT eigenvectors + mean, std, mins

`hyperlearn.impute.SVDImpute.``transform`(X, S, VT, mean, std, mins, standardise, copy=True)

  [Added 31/10/2018] [Edited 2/11/2018 FIxed SVDImpute]

  The fundamental advantage of HyperLearn’s SVD imputation is that a .transform method is provided. I do not require seeing the whole matrix for imputation, and can calculate SVD incrementally via the Incremental Module.

## hyperlearn.metrics.cosine module

`hyperlearn.metrics.cosine.``cosine_dis`

  [Added 22/10/2018] Performs XXT*-1 + 1 quickly on the lower triangular part.

`hyperlearn.metrics.cosine.``cosine_dis_triangular`

  [Added 22/10/2018] Performs XXT*-1 + 1 quickly on the TCSR.

`hyperlearn.metrics.cosine.``cosine_distances`(X, Y=None, triangular=False, n_jobs=1, copy=False)

  [Added 15/10/2018] [Edited 18/10/2018] [Edited 22/10/2018 Added Y option] Note: when using Y, speed improvement is approx 5-10% only from Sklearn.

  Slightly faster than Sklearn’s Cosine Distances implementation. If you set triangular to TRUE, the result is much much faster. (Approx 50% faster than Sklearn)

`hyperlearn.metrics.cosine.``cosine_distances_sparse`(val, colPointer, rowIndices, n, p, triangular=False, dense_output=True, n_jobs=1, copy=True)

  [Added 22/10/2018] Slightly faster than Sklearn’s Cosine Distances implementation.

  If dense_output is set to FALSE, then a TCSR Matrix (Triangular CSR Matrix) is provided and not a CSR matrix. This has the advantage of using only 1/2n^2 - n memory and not n^2 memory.

`hyperlearn.metrics.cosine.``cosine_sim_triangular`(N, D)

  [Added 21/10/2018] Quickly performs X / norm_rows / norm_rows.T on the TCSR matrix.

`hyperlearn.metrics.cosine.``cosine_sim_triangular_parallel`

  [Added 21/10/2018] Quickly performs X / norm_rows / norm_rows.T on the TCSR matrix.

`hyperlearn.metrics.cosine.``cosine_sim_triangular_single`

  [Added 21/10/2018] Quickly performs X / norm_rows / norm_rows.T on the TCSR matrix.

`hyperlearn.metrics.cosine.``cosine_similarity`(X, Y=None, triangular=False, n_jobs=1, copy=False)

  [Added 20/10/2018] [Edited 22/201/2018] [Edited 22/10/2018 Added Y option] Note: when using Y, speed improvement is approx 5% only from Sklearn.

  Cosine similarity is approx the same speed as Sklearn, but uses approx 10% less memory. One clear advantage is if you set triangular to TRUE, then it’s faster.

`hyperlearn.metrics.cosine.``cosine_similarity_sparse`(val, colPointer, rowIndices, n, p, triangular=False, dense_output=True, n_jobs=1, copy=True)

  [Added 20/10/2018] [Edited 21/10/2018] Slightly faster than Sklearn’s Cosine Similarity implementation.

  If dense_output is set to FALSE, then a TCSR Matrix (Triangular CSR Matrix) is provided and not a CSR matrix. This has the advantage of using only 1/2n^2 - n memory and not n^2 memory.

## hyperlearn.metrics.euclidean module

`hyperlearn.metrics.euclidean.``euclidean_distances`(X, Y=None, triangular=False, squared=False, n_jobs=1)

  [Added 15/10/2018] [Edited 16/10/2018] [Edited 22/10/2018 Added Y option] Notice: parsing in Y will result in only 10% - 15% speed improvement, not 30%.

  Much much faster than Sklearn’s implementation. Approx not 30% faster. Probably even faster if using n_jobs = -1. Uses the idea that distance(X, X) is symmetric, and thus algorithm runs only on 1/2 triangular part.

  Old complexity:

    X @ XT n^2p rowSum(X^2) np XXT*-2 n^2 XXT+X^2 2n^2 maximum(XXT,0) n^2

    > n^2p + 4n^2 + np

  New complexity:

    sym X @ XT n^2p/2 rowSum(X^2) np sym XXT*-2 n^2/2 sym XXT+X^2 n^2 maximum(XXT,0) n^2/2

    > n^2p/2 + 2n^2 + np

  So New complexity approx= 1/2(Old complexity)

`hyperlearn.metrics.euclidean.``euclidean_distances_sparse`(val, colPointer, rowIndices, n, p, triangular=False, dense_output=True, squared=False, n_jobs=1)

  [Added 15/10/2018] [Edited 21/10/2018] Much much faster than Sklearn’s implementation. Approx not 60% faster. Probably even faster if using n_jobs = -1 (actually 73% faster). [n = 10,000 p = 1,000] Uses the idea that distance(X, X) is symmetric, and thus algorithm runs only on 1/2 triangular part. Also notice memory usage is now 60% better than Sklearn.

  If dense_output is set to FALSE, then a TCSR Matrix (Triangular CSR Matrix) is provided and not a CSR matrix. This has the advantage of using only 1/2n^2 - n memory and not n^2 memory.

  Old complexity:

    X @ XT n^2p rowSum(X^2) np XXT*-2 n^2 XXT+X^2 2n^2 maximum(XXT,0) n^2

    > n^2p + 4n^2 + np

  New complexity:

    sym X @ XT n^2p/2 rowSum(X^2) np sym XXT*-2 n^2/2 sym XXT+X^2 n^2 maximum(XXT,0) n^2/2

    > n^2p/2 + 2n^2 + np

  So New complexity approx= 1/2(Old complexity)

`hyperlearn.metrics.euclidean.``euclidean_triangular`(S, D, squared=False)

  [Added 21/10/2018] Quickly performs -2D + X^2 + X.T^2 on the TCSR matrix. Also applies maximum(D, 0) and then square roots distances if required.

`hyperlearn.metrics.euclidean.``euclidean_triangular_parallel`

  [Added 21/10/2018] Quickly performs -2D + X^2 + X.T^2 on the TCSR matrix. Also applies maximum(D, 0) and then square roots distances if required.

`hyperlearn.metrics.euclidean.``euclidean_triangular_single`

  [Added 21/10/2018] Quickly performs -2D + X^2 + X.T^2 on the TCSR matrix. Also applies maximum(D, 0) and then square roots distances if required.

`hyperlearn.metrics.euclidean.``maximum0`

  [Added 15/10/2018] [Edited 21/10/2018] Computes maxmimum(XXT, 0) faster. Much faster than Sklearn since uses the notion that distance(X, X) is symmetric.

  Steps:
    maximum(XXT, 0)
      Optimised. Instead of n^2 operations, does n(n-1)/2 operations.

`hyperlearn.metrics.euclidean.``maximum0_parallel`

  [Added 15/10/2018] [Edited 21/10/2018] Computes maxmimum(XXT, 0) faster. Much faster than Sklearn since uses the notion that distance(X, X) is symmetric.

  Steps:
    maximum(XXT, 0)
      Optimised. Instead of n^2 operations, does n(n-1)/2 operations.

`hyperlearn.metrics.euclidean.``mult_minus2`

  [Added 17/10/2018] Quickly multiplies XXT by -2. Uses notion that XXT is symmetric, hence only lower triangular is multiplied.

## hyperlearn.metrics.pairwise module

## hyperlearn.sparse.base module

`hyperlearn.sparse.base.``CreateCSR`(X, n_jobs=1)

  [Added 10/10/2018] [Edited 13/10/2018] Much much faster than Scipy. In fact, HyperLearn uses less memory, by noticing indices >= 0, hence unsigned ints are used.

  Likewise, parallelisation is seen possible with Numba with n_jobs. Notice, an error message will be provided if 20% of the data is only zeros. It needs to be more than 20% zeros for CSR Matrix to shine.

`hyperlearn.sparse.base.``create_csr`(X, rowCount, nnz, temp)

  [Added 10/10/2018] [Edited 13/10/2018] Before used extra memory keeping a Boolean Matrix (np bytes) and a ColIndex pointer which used p memory. Now, removed (np + p) memory usage, meaning larger matrices can be handled.

  Algorithm is 3 fold:

  1. Create RowIndices
  2. For every row in data:
       3. Store until a non 0 is seen.

  Algorithm takes approx O(n + np) time, which is similar to Scipy’s. The only difference is now, parallelisation is possible, which can cut the time to approx O(n + np/c) where c = no of threads

`hyperlearn.sparse.base.``create_csr_cache`

  [Added 10/10/2018] [Edited 13/10/2018] Before used extra memory keeping a Boolean Matrix (np bytes) and a ColIndex pointer which used p memory. Now, removed (np + p) memory usage, meaning larger matrices can be handled.

  Algorithm is 3 fold:

  1. Create RowIndices
  2. For every row in data:
       3. Store until a non 0 is seen.

  Algorithm takes approx O(n + np) time, which is similar to Scipy’s. The only difference is now, parallelisation is possible, which can cut the time to approx O(n + np/c) where c = no of threads

`hyperlearn.sparse.base.``create_csr_parallel`

  [Added 10/10/2018] [Edited 13/10/2018] Before used extra memory keeping a Boolean Matrix (np bytes) and a ColIndex pointer which used p memory. Now, removed (np + p) memory usage, meaning larger matrices can be handled.

  Algorithm is 3 fold:

  1. Create RowIndices
  2. For every row in data:
       3. Store until a non 0 is seen.

  Algorithm takes approx O(n + np) time, which is similar to Scipy’s. The only difference is now, parallelisation is possible, which can cut the time to approx O(n + np/c) where c = no of threads

`hyperlearn.sparse.base.``determine_nnz`

  Uses close to no memory at all when computing how many non zeros are in the matrix. Notice the difference with Scipy is HyperLearn does NOT use nonzero(). This reduces memory usage dramatically.

`hyperlearn.sparse.base.``getDtype`(p, size, uint=True)

  Computes the exact best possible data type for CSR Matrix creation.

## hyperlearn.sparse.csr module

`hyperlearn.sparse.csr.``XXT_sparse`(val, colPointer, rowIndices, n, p)

  See _XXT_sparse documentation.

`hyperlearn.sparse.csr.``add_0`

`hyperlearn.sparse.csr.``add_1`

`hyperlearn.sparse.csr.``add_A`

`hyperlearn.sparse.csr.``diagonal`

  [Added 10/10/2018] [Edited 13/10/2018] Extracts the diagonal elements of a CSR Matrix. Note only gets square diagonal (not off-diagonal). HyperLearn’s algorithm is faster than Scipy’s, as it uses binary search, whilst Scipy uses Linear Search.

  d = min(n, p) HyperLearn = O(d log p) Scipy = O(dp)

`hyperlearn.sparse.csr.``diagonal_add`(val, colPointer, rowIndices, n, p, addon, copy=True)

  See _diagonal_add documentation.

`hyperlearn.sparse.csr.``div_0`

`hyperlearn.sparse.csr.``div_1`

`hyperlearn.sparse.csr.``div_A`

`hyperlearn.sparse.csr.``get_element`

  [Added 14/10/2018] Get A[i,j] element. HyperLearn’s algorithm is O(logp) complexity, which is much better than Scipy’s O(p) complexity. HyperLearn uses binary search to find the element.

`hyperlearn.sparse.csr.``matT_mat`

  Added [14/10/2018] A.T @ X is found where X is a dense matrix. Mostly the same as Scipy, albeit slightly faster. The difference is now, HyperLearn is parallelized, which can reduce times by 1/2 or more.

`hyperlearn.sparse.csr.``matT_mat_parallel`

  Added [14/10/2018] A.T @ X is found where X is a dense matrix. Mostly the same as Scipy, albeit slightly faster. The difference is now, HyperLearn is parallelized, which can reduce times by 1/2 or more.

`hyperlearn.sparse.csr.``matT_vec`

  Added [13/10/2018] X.T @ y is found. Notice how instead of converting CSR to CSC matrix, a direct X.T @ y can be found. Same complexity as mat_vec(X, y). Also, HyperLearn is parallelized, allowing for O(np/c) complexity.

`hyperlearn.sparse.csr.``matT_vec_parallel`

  Added [13/10/2018] X.T @ y is found. Notice how instead of converting CSR to CSC matrix, a direct X.T @ y can be found. Same complexity as mat_vec(X, y). Also, HyperLearn is parallelized, allowing for O(np/c) complexity.

`hyperlearn.sparse.csr.``mat_mat`

  Added [14/10/2018] A @ X is found where X is a dense matrix. Mostly the same as Scipy, albeit slightly faster. The difference is now, HyperLearn is parallelized, which can reduce times by 1/2 or more.

`hyperlearn.sparse.csr.``mat_mat_parallel`

  Added [14/10/2018] A @ X is found where X is a dense matrix. Mostly the same as Scipy, albeit slightly faster. The difference is now, HyperLearn is parallelized, which can reduce times by 1/2 or more.

`hyperlearn.sparse.csr.``mat_vec`

  Added [13/10/2018] X @ y is found. Scipy & HyperLearn has similar speed. Notice, now HyperLearn can be parallelised! This reduces complexity to approx O(np/c) where c = no of threads / cores

`hyperlearn.sparse.csr.``mat_vec_parallel`

  Added [13/10/2018] X @ y is found. Scipy & HyperLearn has similar speed. Notice, now HyperLearn can be parallelised! This reduces complexity to approx O(np/c) where c = no of threads / cores

`hyperlearn.sparse.csr.``max_0`

`hyperlearn.sparse.csr.``max_1`

`hyperlearn.sparse.csr.``max_A`

`hyperlearn.sparse.csr.``mean_0`

`hyperlearn.sparse.csr.``mean_1`

`hyperlearn.sparse.csr.``mean_A`

`hyperlearn.sparse.csr.``min_0`

`hyperlearn.sparse.csr.``min_1`

`hyperlearn.sparse.csr.``min_A`

`hyperlearn.sparse.csr.``mult_0`

`hyperlearn.sparse.csr.``mult_1`

`hyperlearn.sparse.csr.``mult_A`

`hyperlearn.sparse.csr.``rowSum`

  [Added 17/10/2018] Computes rowSum**2 for sparse matrix efficiently, instead of using einsum

`hyperlearn.sparse.csr.``sum_0`

`hyperlearn.sparse.csr.``sum_1`

`hyperlearn.sparse.csr.``sum_A`

## hyperlearn.sparse.tcsr module

## Module contents