# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['yodf']

package_data = \
{'': ['*']}

install_requires = \
['numpy>=1.19.4,<2.0.0']

setup_kwargs = {
    'name': 'yodf',
    'version': '1.0.13',
    'description': "'Hello, World!' Forward Mode Autdiff library with Tensorflow 1 like interface.",
    'long_description': '## A \'Hello, World!\' forward mode autodiff library.\n\nThis small (~500 lines) library is meant as an illustration of how forward mode autodiff can possibly be implemented. It lets you compute the value and the derivative of a function expressed as a computational flow using the primitives provided by the library. Interface of the library is very similar to Tensorflow 1.\\*. All the samples provided in _examples_ folder can very well be run if you do **import tensorflow as tf** as opposed to **import yodf as tf** It supports following operations { "add", "subtract", "divide", "multiply", "pow", "sin", "cos", "log", "exp", "matmul", "sigmoid", "reduce_mean", "reduce_sum" }.\n\n### Installation\n\n**pip install yodf** will install the library. Only dependency it has is _numpy_. Samples provided in examples folder also have dependency on _matplotlib_ and _scipy_.\n\n### Basic usage\n\nBelow code computes the value and the derivative of the function **x^2** at x=5.0\n\n```\nimport yodf as tf\nx = tf.Variable(5.0)\ncost = x**2\nwith tf.Session() as s:\n    # global_variables_initializer API added just so as to\n    # resemble Tensorflow, it hardly does anything\n    s.run(tf.global_variables_initializer())\n    s.run(cost)\nprint(x.value, cost.value, cost.gradient)\n\n## Output\n## 5.0 25.0 10.0\n```\n\n### Basic gradient descent example\n\nBelow code computes optima of the function **x^2** along with the value at which optima occurs starting with x=5.0\n\n```\nimport yodf as tf\nx = tf.Variable(5.0)\ncost = x**2\ntrain = tf.train.GradientDescentOptimizer(learning_rate=0.2).minimize(cost)\nwith tf.Session() as s:\n    s.run(tf.global_variables_initializer())\n    for _ in range(50):\n        _, cost_final, x_final = s.run([train, x, cost])\nprint(f"Minima: {cost_final:.10f}, x at minima: {x_final:.10f}")\n\n## Output\n## Minima: 0.0000000000, x at minima: 0.0000000000\n```\n\n## How does it work?\n\nIt has a class called _Tensor_ with _Variable_ and _\\_Constant_ as subclasses. Tensor object holds a value and a gradient. Gradient of a constant is 0 and that of a variable is 1 which is as good as saying d(x)/dx.  \nA tensor can also represent an operation and a tensor representating an operation gets created using a convenient function call like _tf.sin()_ or _tf.matmul()_ etc.\n\n```\nimport numpy as np\nimport yodf as tf\nx = tf.Variable(np.array([[1,1],[2,2]]))\nop_sin = tf.sin(x)\nprint(op_sin)\n\n## Output\n## <yod.Tensor type=TensorType.INT, shape=(2, 2), operation=\'sin\'>\n```\n\nYou typically pass a tensor to run method of _Session_ class which ends up evaluating the tensor along with its derivative. Execute method of tensor just knows how to compute derivative of basic arithmatic operations, power function and some of the transcendental functions like sin, cos, log, exp. It also knows how to compute derivative when matrix multiplication operation is involved. By applying the chain rule repeatedly to these operations, derivative of an arbitrary function (represented as a tensor) gets computed automatically. _run_ method simply builds post order traversal tree of the tensor passed to it and evaluates all the nodes in the tree. _GradientDescentOptimizer_ simply updates the value of the variable based on the gradient of the cost tensor passed to its minimize function.  \nWith multiple independent variables, partial derivative of one variable gets computed at a time while the gradient of rest of the variables is set to 0. This, in turn, is done for all the variables and partial derivatives or the gradients of all the vatiables are accumulated by _GradientDescentOptimizer_ which is not necessarily very clean.\n\n## Examples\n\nExamples folder shows use of this library for\n\n1. <a href="https://github.com/yogimogi/yodf/blob/master/examples/example1_simple_cost_function.ipynb">A gradient descent problem for a simple cost function</a>\n2. <a href="https://github.com/yogimogi/yodf/blob/master/examples/example2_cost_function_2_variables.ipynb">A gradient descent problem for a simple cost function with 2 independent variables</a>\n3. <a href="https://github.com/yogimogi/yodf/blob/master/examples/example3_linear_regression.ipynb">A linear regression problem</a>\n4. <a href="https://github.com/yogimogi/yodf/blob/master/examples/example4_logistic_regression.ipynb">A logistic regression problem</a>\n5. <a href="https://github.com/yogimogi/yodf/blob/master/examples/example5_neural_network.ipynb">A neural network with one hidden layer and one output</a>\n6. <a href="https://github.com/yogimogi/yodf/blob/master/examples/example6_neural_network_mnist.ipynb">A neural network with one hidden layer and 10 outputs (MNIST digit classification)</a>\n\n## Limitiation of forward mode autodiff\n\nThough with forward mode autodiff, derivative of a function with one independent variables gets computed during forward pass itself and no backward pass is needed as is the case with reverse mode autodiff (generalized backpropagation), with multiple indepdent variables (say weights in a neural network), as many passes are needed as number of indepdent variables. So as can be seen in <a href="https://github.com/yogimogi/yodf/blob/master/examples/example3_linear_regression.ipynb">linear regression sample</a>, time needed by gradient descent linearly increases with increase in degree of polynomial you are trying to fit. For MNIST digit classification, this library becomes almost unusable due to large number of independent variables whose gradient needs to be computed.\n',
    'author': 'Yogesh Ketkar',
    'author_email': 'yogimogi@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/yogimogi/yodf',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.6,<4.0',
}


setup(**setup_kwargs)
