{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Simple example demonstrating use of tred library\n",
    "This is a rough demonstration of the basic functionalities\n",
    "in `tred`. It unfortunately, at the moment, does not cover all of the public \n",
    "functionality. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from numpy.testing import assert_allclose\n",
    "\n",
    "import tred\n",
    "from tred import display_tensor_facewise as disp"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First we will create some dummy data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# let's use a numpy rng\n",
    "RNG = np.random.default_rng(seed=1234)\n",
    "\n",
    "# then we will generate some dummy data\n",
    "n, p, t = 3, 4, 2\n",
    "X = RNG.random(size=(n, p, t))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's quickly view our data using the `display_tensor_facewise` function, which we imported as `disp`. Note: this looks a bit nicer than calling `print(X)`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tensor with shape (3, 4, 2)\n",
      "[[[0.9767   0.923246 0.319097 0.241766]\n",
      "  [0.964079 0.441006 0.863621 0.674881]\n",
      "  [0.735758 0.172066 0.060139 0.671238]]\n",
      "\n",
      " [[0.380196 0.261692 0.118091 0.318534]\n",
      "  [0.26365  0.609871 0.863758 0.659874]\n",
      "  [0.222754 0.870415 0.683689 0.611018]]]\n"
     ]
    }
   ],
   "source": [
    "disp(X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## tsvdm tensor decomposition algorithm\n",
    "Let's get the tsvdm decomposition first, using the `tsvdm` function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# so, first we can use tred's tsvdm decomposition\n",
    "# NOTE: we return V not Vt\n",
    "U, S, V = tred.tsvdm(X)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tensor with shape (3, 3, 2)\n",
      "[[[ 0.171555 -0.032544 -0.567501]\n",
      "  [ 0.773763 -0.775055  0.249955]\n",
      "  [ 1.006132  0.036617  0.276475]]\n",
      "\n",
      " [[ 0.496972  1.180533  0.082534]\n",
      "  [ 0.219636 -0.048142 -0.829184]\n",
      "  [-0.253649  0.03023   0.919058]]]\n"
     ]
    }
   ],
   "source": [
    "disp(U)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tensor with shape (3, 4, 2)\n",
      "[[[ 2.490714  0.        0.        0.      ]\n",
      "  [ 0.        0.951338  0.        0.      ]\n",
      "  [ 0.        0.        0.335458  0.      ]]\n",
      "\n",
      " [[ 1.295923  0.        0.        0.      ]\n",
      "  [ 0.       -0.060585  0.        0.      ]\n",
      "  [ 0.        0.        0.109848  0.      ]]]\n"
     ]
    }
   ],
   "source": [
    "disp(S)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tensor with shape (4, 4, 2)\n",
      "[[[ 0.727706 -0.264466 -0.165429 -0.41692 ]\n",
      "  [-0.151391 -0.020989 -0.291684  0.607529]\n",
      "  [-0.017056 -0.610954  0.235092  0.099226]\n",
      "  [ 0.39763  -0.166782  0.608801  0.521138]]\n",
      "\n",
      " [[ 0.024415  0.950528 -0.335921 -0.427048]\n",
      "  [ 0.843444  0.655176  0.433084  0.440744]\n",
      "  [ 0.695221 -0.366154 -0.961108  0.142126]\n",
      "  [ 0.306266 -0.24841   0.487353 -0.882514]]]\n"
     ]
    }
   ],
   "source": [
    "disp(V)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# NOTE: the V tensor is not transposed (facewise)!\n",
    "# in order to do a facewise transpose - use numpy\n",
    "Vt = V.transpose(1, 0, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tensor with shape (4, 4, 2)\n",
      "[[[ 0.727706 -0.151391 -0.017056  0.39763 ]\n",
      "  [-0.264466 -0.020989 -0.610954 -0.166782]\n",
      "  [-0.165429 -0.291684  0.235092  0.608801]\n",
      "  [-0.41692   0.607529  0.099226  0.521138]]\n",
      "\n",
      " [[ 0.024415  0.843444  0.695221  0.306266]\n",
      "  [ 0.950528  0.655176 -0.366154 -0.24841 ]\n",
      "  [-0.335921  0.433084 -0.961108  0.487353]\n",
      "  [-0.427048  0.440744  0.142126 -0.882514]]]\n"
     ]
    }
   ],
   "source": [
    "disp(Vt)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's also verify that we can use the tsvdm decomposition to reconstruct the \n",
    "original tensor `X`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# see documentation - allows us to apply a function over a sequence of inputs \n",
    "# cumulatively from left to right\n",
    "from functools import reduce\n",
    "\n",
    "# tred has a function to comput the m_product, but it does not make default assumptions\n",
    "# about the m-transform - as the user, you need to write a simple wrapper to make it work\n",
    "# with a m-product you want\n",
    "\n",
    "# you can fetch the default m-transforms used by tsvdm and TPCA using the following tred \n",
    "# function\n",
    "M, Minv = tred.generate_default_m_transform_pair(t)\n",
    "\n",
    "# then write our wrapper function\n",
    "def m_product_wrapper(A, B): \n",
    "    return tred.m_product(A, B, M=M, Minv=Minv)\n",
    "\n",
    "# multiply U, S, Vt together using the m-product and default m-transfrom\n",
    "X_reconstructed = reduce(m_product_wrapper, (U, S, Vt))\n",
    "assert_allclose(X_reconstructed, X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__UPDATE__!: If you have ```tred.__version__ >= \"0.1.3\"```, you can do the following..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_reconstructed2 = tred.m_product(U, S, Vt)\n",
    "assert_allclose(X_reconstructed2, X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice how the last column of `S` is all zeroes. This means that some of the\n",
    "`V` tensor is redundant. We can pass in `full_frontal_slices=False` to only\n",
    "return us the necessary elements in the tensor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "((3, 3, 2), (3, 3, 2), (4, 3, 2))"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "U2, S2, V2 = tred.tsvdm(X, full_frontal_slices=False)\n",
    "\n",
    "(U2.shape, S2.shape, V2.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sense check that we have not 'loss' any information, and we can still \n",
    "fully reconstruct our original tensor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "V2t = V2.transpose(1, 0, 2)\n",
    "X_reconstructed2 = reduce(m_product_wrapper, (U2, S2, V2t))\n",
    "assert_allclose(X_reconstructed2, X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## TPCA unsupervised tensor dimensionality reduction algorithm\n",
    "Now, let's use the tensor decomposition using the `tpca` object. Revise some basics of object oriented programming and scikit-learn's interface if you are coming from R."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "# first, create a transformer object\n",
    "tpca = tred.TPCA()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There's two ways to do this:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. You explicitly fit your tpca object _first_, and then transform your data.\n",
    "\n",
    "Let's see what happens if you call `transform` _before_ `fit`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "ename": "NotFittedError",
     "evalue": "This TPCA instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator.",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mNotFittedError\u001b[0m                            Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[15], line 2\u001b[0m\n\u001b[0;32m      1\u001b[0m \u001b[38;5;66;03m# ERROR\u001b[39;00m\n\u001b[1;32m----> 2\u001b[0m X_transformed_1 \u001b[38;5;241m=\u001b[39m \u001b[43mtpca\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtransform\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32mc:\\Users\\brend\\OneDrive\\Documents\\GITHUB STUFF\\tred\\.venv\\lib\\site-packages\\sklearn\\utils\\_set_output.py:157\u001b[0m, in \u001b[0;36m_wrap_method_output.<locals>.wrapped\u001b[1;34m(self, X, *args, **kwargs)\u001b[0m\n\u001b[0;32m    155\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(f)\n\u001b[0;32m    156\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapped\u001b[39m(\u001b[38;5;28mself\u001b[39m, X, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m--> 157\u001b[0m     data_to_wrap \u001b[38;5;241m=\u001b[39m f(\u001b[38;5;28mself\u001b[39m, X, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m    158\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(data_to_wrap, \u001b[38;5;28mtuple\u001b[39m):\n\u001b[0;32m    159\u001b[0m         \u001b[38;5;66;03m# only wrap the first output for cross decomposition\u001b[39;00m\n\u001b[0;32m    160\u001b[0m         return_tuple \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m    161\u001b[0m             _wrap_data_with_container(method, data_to_wrap[\u001b[38;5;241m0\u001b[39m], X, \u001b[38;5;28mself\u001b[39m),\n\u001b[0;32m    162\u001b[0m             \u001b[38;5;241m*\u001b[39mdata_to_wrap[\u001b[38;5;241m1\u001b[39m:],\n\u001b[0;32m    163\u001b[0m         )\n",
      "File \u001b[1;32m~\\OneDrive\\Documents\\GITHUB STUFF\\tred\\tred\\_tensor_pca.py:284\u001b[0m, in \u001b[0;36mTPCA.transform\u001b[1;34m(self, X)\u001b[0m\n\u001b[0;32m    264\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtransform\u001b[39m(\u001b[38;5;28mself\u001b[39m, X):\n\u001b[0;32m    265\u001b[0m \u001b[38;5;250m    \u001b[39m\u001b[38;5;124;03m\"\"\"Apply dimensionality reduction to X.\u001b[39;00m\n\u001b[0;32m    266\u001b[0m \n\u001b[0;32m    267\u001b[0m \u001b[38;5;124;03m    See the TCAM algorithm from Mor et al. (2022)\u001b[39;00m\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m    281\u001b[0m \u001b[38;5;124;03m        TCAM projections in 2D transformed space.\u001b[39;00m\n\u001b[0;32m    282\u001b[0m \u001b[38;5;124;03m    \"\"\"\u001b[39;00m\n\u001b[1;32m--> 284\u001b[0m     \u001b[43mcheck_is_fitted\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[0;32m    285\u001b[0m     \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(X\u001b[38;5;241m.\u001b[39mshape) \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m3\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mEnsure order-3 tensor input\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m    286\u001b[0m     \u001b[38;5;28;01massert\u001b[39;00m (\n\u001b[0;32m    287\u001b[0m         X\u001b[38;5;241m.\u001b[39mshape[\u001b[38;5;241m1\u001b[39m] \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mp_ \u001b[38;5;129;01mand\u001b[39;00m X\u001b[38;5;241m.\u001b[39mshape[\u001b[38;5;241m2\u001b[39m] \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mt_\n\u001b[0;32m    288\u001b[0m     ), \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mEnsure the number of features, and time points, matches the model fit data\u001b[39m\u001b[38;5;124m\"\u001b[39m\n",
      "File \u001b[1;32mc:\\Users\\brend\\OneDrive\\Documents\\GITHUB STUFF\\tred\\.venv\\lib\\site-packages\\sklearn\\utils\\validation.py:1461\u001b[0m, in \u001b[0;36mcheck_is_fitted\u001b[1;34m(estimator, attributes, msg, all_or_any)\u001b[0m\n\u001b[0;32m   1458\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m is not an estimator instance.\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m%\u001b[39m (estimator))\n\u001b[0;32m   1460\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m _is_fitted(estimator, attributes, all_or_any):\n\u001b[1;32m-> 1461\u001b[0m     \u001b[38;5;28;01mraise\u001b[39;00m NotFittedError(msg \u001b[38;5;241m%\u001b[39m {\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mname\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;28mtype\u001b[39m(estimator)\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m})\n",
      "\u001b[1;31mNotFittedError\u001b[0m: This TPCA instance is not fitted yet. Call 'fit' with appropriate arguments before using this estimator."
     ]
    }
   ],
   "source": [
    "%%capture\n",
    "# ERROR\n",
    "X_transformed_1 = tpca.transform(X)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "# so, we need to fit the tpca object first - then call transform!\n",
    "tpca.fit(X)\n",
    "X_transformed_1 = tpca.transform(X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Bit of a late update - but `disp` (named `display_tensor_facewise`) actually \n",
    "works for matrix and vector inputs too. It just does a bit of rounding and \n",
    "prints the size of the input. We have left the unfortunate original name as is, \n",
    "to avoid breaking existing workflows. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix with shape (3, 6)\n",
      "[[ 0.56846   0.511809 -0.154348 -0.068988  0.       -0.      ]\n",
      " [-0.006665 -0.559724 -0.117835  0.140461 -0.       -0.      ]\n",
      " [-0.561795  0.047915  0.272183 -0.071473  0.        0.      ]]\n"
     ]
    }
   ],
   "source": [
    "# the size of this is n rows by min(n, p) columns\n",
    "disp(X_transformed_1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. Use `fit_transform`\n",
    "\n",
    "NOTE: this produces the same result to machine precision, but it's not identical, as we \n",
    "use a slightly more efficient computation to obtain the transformed data. \n",
    "\n",
    "See the `tred` library implementation for more details"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_transformed_2 = tpca.fit_transform(X)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix with shape (3, 6)\n",
      "[[ 0.56846   0.511809 -0.154348 -0.068988 -0.        0.      ]\n",
      " [-0.006665 -0.559724 -0.117835  0.140461 -0.        0.      ]\n",
      " [-0.561795  0.047915  0.272183 -0.071473 -0.        0.      ]]\n"
     ]
    }
   ],
   "source": [
    "# NOTE: this is the same as the array above - to machine precision\n",
    "# some numbers might LOOK different, but you'll see that they are tiny and\n",
    "# are effectively the same\n",
    "disp(X_transformed_2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "# the tensor operations produce some floating point errors\n",
    "# below are the tolerances we use in tred's testing module, which show that the two\n",
    "# transformations of X are indeed the same\n",
    "RTOL = 1e-7\n",
    "ATOL = 1e-10\n",
    "assert_allclose(X_transformed_1, X_transformed_2, rtol=RTOL, atol=ATOL)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can use the fitted objects to transform out of bag data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "# imagine one new observation, with p features, across t time points\n",
    "new_obs = RNG.random(size=(1, p, t))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tensor with shape (1, 4, 2)\n",
      "[[[0.060137 0.438952 0.003132 0.85849 ]]\n",
      "\n",
      " [[0.977769 0.532595 0.251267 0.425298]]]\n"
     ]
    }
   ],
   "source": [
    "disp(new_obs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix with shape (1, 6)\n",
      "[[-0.13546   0.34662   0.397189 -0.553042  0.950862  0.078852]]\n"
     ]
    }
   ],
   "source": [
    "disp(tpca.transform(new_obs))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Make sure you do not call `fit` or `fit_transform` on the new data, otherwise \n",
    "the `tpca` object will learn new weights!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
