{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Automatic differentiation\n",
    "\n",
    "Being based on `pytorch`, `torchtt` can handle automatic differentiation with respect to the TT cores. \n",
    "\n",
    "\n",
    "Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch as tn\n",
    "try: \n",
    "    import torchtt as tntt\n",
    "except:\n",
    "    print('Installing torchTT...')\n",
    "    %pip install git+https://github.com/ion-g-ion/torchTT\n",
    "    import torchtt as tntt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First, a function to differentiate is created and some tensors:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "N = [2,3,4,5]\n",
    "A = tntt.randn([(n,n) for n in N],[1]+[2]*(len(N)-1)+[1])\n",
    "y = tntt.randn(N,A.R)\n",
    "x = tntt.ones(N)\n",
    "\n",
    "def f(x,A,y):\n",
    "    z = tntt.dot(A @ (x-y),(x-y))\n",
    "    return z.norm()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In order to compute the derivative of a scalar with respect to all cores of a TT object, the AD graph recording has to be started:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tntt.grad.watch(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using the `torchtt.grad.grad()` method, the gradient is computed:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "val = f(x,A,y)\n",
    "grad_cores = tntt.grad.grad(val, x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The variable `grad_cores` is a list of tensors representing the derivatives of `f()` with resect to the individual core entries.\n",
    "For checking, we compute the derivative of teh function with respect to one element of the core"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "h = 1e-7\n",
    "x1 = x.clone()\n",
    "x1.cores[1][0,0,0] += h\n",
    "x2 = x.clone()\n",
    "x2.cores[1][0,0,0] -= h\n",
    "derivative = (f(x1,A,y)-f(x2,A,y))/(2*h)\n",
    "print(tn.abs(derivative-grad_cores[1][0,0,0])/tn.abs(derivative))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The functions `torchtt.grad.grad()` and `torchtt.grad.watch()` can take an additional list of modes `core_indices` as argument which decides which cores are watched and differentiaated with respect to."
   ]
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "df6fc3a9b7a9c6f4b0308ab6eb361a4cabbf6b5db181383d07014ff4304e5cb3"
  },
  "kernelspec": {
   "display_name": "Python 3.9.7 64-bit ('pytorch': conda)",
   "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.8.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
