\n\n

\n\nOur implementation of AD uses dual numbers to calculate derivatives of individual components. Dual numbers have real and dual components, taking the form ![equation](https://latex.codecogs.com/gif.image?%5Cbg_white%5Cinline%20%5Cdpi%7B110%7Da%20+%20b%5Cepsilon$%20with%20$%5Cepsilon%5E2%20=%200) and ![equation](https://latex.codecogs.com/gif.image?%5Cbg_white%5Cinline%20%5Cdpi%7B110%7D%5Cepsilon%20%5Cneq%200) and where `a` and `b` are real. By the Taylor series expansion of a function around a point, notice that evaluating a function at ![equation](https://latex.codecogs.com/gif.image?%5Cbg_white%5Cinline%20%5Cdpi%7B110%7Da%20+%20%5Cepsilon) yields:\n\n\n\n

\n\nHence, by evaluating the function at the desired point ![equation](https://latex.codecogs.com/gif.image?%5Cbg_white%5Cinline%20%5Cdpi%7B110%7Da%20+%20%5Cepsilon), the outputted real and dual components are the function evaluated at `a` and derivative of the function evaluated at `a` respectively. This is an efficient way of calculating requisite derivatives. \n\n## How to Use\nFirst, ensure that you are using Python 3.10 or newer. All future steps can/should be completed in a virtual environment so as not to pollute your base Python installation. To create and activate a new virtual environment, use the following:\n```\npython3 -m venv [desired/path/to/venv]\nsource [desired/path/to/venv]/bin/activate\n```\n\nNext, clone the package from this GitHub repository and install the needed dependencies and the package:\n\n```\ngit clone https://code.harvard.edu/CS107/team33.git\npython3 -m pip install -r requirements.txt\npython3 -m pip install .\n```\n\nNow, you're ready to use the package. Continue to the [Example](#Example) to test our the package!\n\n### Example\n\nFirst, import the package in your python code:\n```\nimport Adifpy\n```\n\nand create an `Evaluator` object, which takes a callable function as an argument:\n```\nevaluator = Adifpy.Evaluator(lambda x : x**2)\n```\n\nNext, we want to find the value and derivative of the function at a point (currently, only scalar functions with 1 input and 1 output are supported). We can use the `Evaluator`'s `eval` function, passing in the point at which you want to evaluate (and optionally, a scalar seed vector):\n```\noutput = evaluator.eval(3)\n```\n\nThis function returns a tuple, in the form `(value, derivative)`, where the value is the evaluation of the function at that point (in this case, 9) and the derivative is the derivative of the function at that point (in this case, 6).\n\nAdditionally a seed vector (for now, only scalars such as type `int` or `float` are supported) can be passed to take the derivative with respect to a different seed vector. For example, if you want to take the derivative with respect to a seed vector of `2` you could call the following:\n```\noutput2 = evaluator.eval(3, seed_vector=2)\n```\nwhich would return `(9,12)` (since the directional derivative is in the same direction, with twice the magnitude).\n\n## Software Organization\nThe following section outlines our plans for organizing the package directory, sub-packages, modules, classes, and deployment.\n\n### Directory Structure\n\nadifpy/\n\u251c\u2500\u2500 docs\n\u2502 \u2514\u2500\u2500 milestone1\n\u2502 \u2514\u2500\u2500 milestone2\n\u2502 \u2514\u2500\u2500 milestone2_progress\n\u2502 \u2514\u2500\u2500 documentation\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 requirements.txt\n\u251c\u2500\u2500 pyproject.toml\n\u251c\u2500\u2500 Adifpy\n\u2502 \u251c\u2500\u2500 differentiate\n\u2502 \u2502 \u2514\u2500\u2500 dual_number.py\n\u2502 \u2502 \u2514\u2500\u2500 evaluator.py \n\u2502 \u2502 \u2514\u2500\u2500 forward_mode.py\n\u2502 \u2502 \u2514\u2500\u2500 function_tree.py \n\u2502 \u2502 \u2514\u2500\u2500 reverse_mode.py\n\u2502 \u251c\u2500\u2500 visualize\n\u2502 \u2502 \u2514\u2500\u2500 graph_function.py \n\u2502 \u251c\u2500\u2500 test \n\u2502 \u2502 \u2514\u2500\u2500 README.md\n\u2502 \u2502 \u2514\u2500\u2500 run_tests.sh\n\u2502 \u2502 \u2514\u2500\u2500 test_dual_number.py\n\u2502 \u2502 \u2514\u2500\u2500 ... (unit and integration tests)\n\u2502 \u251c\u2500\u2500 __init__.py\n\u2502 \u2514\u2500\u2500 config.py\n\u2514\u2500\u2500 .github\n \u2514\u2500\u2500 workflows\n \u2514\u2500\u2500 coverage.yaml\n \u2514\u2500\u2500 test.yaml\n\n\n### Subpackages\n\nThe `Adifpy` directory contains the source code of our package, which contains 3 subpackages: `differentiate`, `visualize`, and `test`, described below.\n\n#### Differentiate\n\nThe differentiate subpackage currently contains modules required to perform forward mode AD on functions from R to R. Contained in this subpackage are the modules `dual_number.py`, `elementary_functions.py`, `evaluator.py`, `forward_mode.py`, `function_tree.py`, and `reverse_mode.py`. For more information on each module, see [Modules and Classes](#Modules-and-Classes).\n\n#### Visualize\n\nThis subpackage has not been implemented yet. Check out our implementation plan [below](#Visualization).\n\n#### Test\n\nThe test suite is contained in the test sub-package, as shown above in the [Directory Structure](#Directory-Structure). The test directory contains a `run_tests.sh`, which installs the package and runs the relevant `pytest` commands to display data on the testing suite (similar to the CI workflows).\n\nThe individual test files, each of which are named in the `test_*.py` format, test a different aspect of the package. Within each file, each function (also named `test_*`) tests a smaller detail of that aspect. For example, the `test_dual_number.py` test module tests the implementation of the `DualNumber` class. Each function in that module tests one of the overloaded operators. Thus, error messaging will be comprehensive, should one of these operators be changed and fail to work.\n\nThe easiest way to run the test suite is to go to the `test` directory and run `./run_tests.sh`.\n\n## Implementation\n\nMajor data structures, including descriptions on how dual numbers are implemented, are described in the [Modules and Classes](#Modules-and-Classes) section below.\n\n\n### Libraries\nThe `differentiate` sub-package requires the `NumPy` library. Additionally, the `visualization` sub-package will require `MatPlotLib` for displaying graphs. Additional libraries may be required later for additional ease of computation or visualization.\n\nThese requirements are specified in the `requirements.txt` for easy installation.\n\n\n### Modules and Classes\n\n#### `dual_number.py`\nthe `DualNumber` class, stored in this module, contains the functionality for dual numbers for automatic differentiation. When a forward pass (in forward mode) is performed on a user function, a `DualNumber` object is passed to mimic the function's numeric or vector input. All of `DualNumber`'s major mathematical dunder methods are overloaded so that the `DualNumber` is updated for each of the function's elementary operations.\n\nEach of the binary dunder methods (addition, division, etc.) work with both other numeric types (integers and floats) as well as other `DualNumber`s.\n\n#### `evaluator.py`\nThe `Evaluator` class, stored in this module, is the user's main communication with the package. An `Evaluator` object is defined by its function, which is provided by the user on creation. A derivative can be calculated at any point, with any seed vector, by calling an `Evaluator`'s `eval` function. The `Evaluator` class ensures that a user's function is valid, decides whether to use forward or reverse mode (based on performance), and returns the derivative on `eval` calls.\n\n*When reverse mode is implemented, the `Evaluator` class may also contain optimizations for making future `eval` calls faster by storing a computational graph.*\n\n#### `forward_mode.py`\nThis module contains only the `forward_mode` method, which takes a user function, evaluation point, and seed vector. Its implementation is incredibly simple: a `DualNumber` is created with the real part as the evaluation point and the dual part as the seed vector. This `DualNumber` is then passed through the user's function, and the resulting real and dual components of the output `DualNumber` are the function output and derivative.\n\n#### `function_tree.py`\nThe `FunctionTree` class, stored in this module, is a representation of a computational graph in the form of a tree, where intermediate variables are stored as nodes. The parent-child relationship between these nodes represents the elementary operations for these intermediate variables. This class contains optimizations like ensuring duplicate nodes are avoided.\n\n*This module is currently unused (and un-implemented). When reverse mode is implemented, a given `Evaluator` object will build up and store a `FunctionTree` for optimization.*\n\n#### `reverse_mode.py`\nThis module contains only the `reverse_mode` method, which takes the same arguments as `forward_pass`. This function is not yet implemented.\n\n#### `graph_tree.py`\nThis module will contain functionality for displaying a presentable representation of a computation graph in an image. Using a `FunctionTree` object, the resulting image will be a tree-like structure with nodes and connections representing intermediate variables and elementary operations. This functionality is not yet implemented.\n\n#### `graph_function.py`\nThis module will contain functionality for graphing a function and its derivative. It will create an `Evaluator` object and make the necessary `eval` calls to fill a graph for display. This functionality is not yet implemented.\n\n\n### Elementary Functions\nMany elementary functions like trigonometric, inverse trigonometric and exponential cannot be overloaded by Python's dunder methods (like addition and subtraction can). However, a user must still be able to use these operators in their functions, but cannot use the standard `math` or `np` versions, since a `DualNumber` object is passed to the function for forward passes.\n\nThus, we define a module `elementary_functions.py` that contains methods which take a `DualNumber`, and return a `DualNumber`, with the real part equal to the elementary operation applied to the real part, and the derivative of the operation applied to the dual part. Thus, these functions are essentially our package's **storage** for the common derivatives (cosine is the derivative of sine, etc.), where the storage of the derivative is the assignment of the dual part of the output of these elementary operations.\n\nThese operations will be automatically imported in the package's `__init__.py` so that users can simply call `Adifpy.sin()` or `Adifpy.cos()` (for this milestone our implementation requires users to call `ef.sin()` and `ef.cos()`, not `Adifpy.sin()` or `Adifpy.cos()`), as they would with `np.sin()` and `np.cos()`.\n\n## Extension\nNow that our forward mode implementation is complete, we will move on to implement additional features and conveniences for the user.\n\n### Reverse Mode\nWe will implement reverse mode AD in the differentiate subpackage. Given that we have already been quizzed on the background math, encoding this process should not be too onerous. One of the biggest challenges that we foresee is determining when it is best to use Reverse Mode and when it is best to use Forward Mode. Obviously, it is better to use forward mode when there are far more outputs than inputs and vice-versa for reverse mode, but in the cases where number of inputs and outputs are similar it is not so simple. To address this we will do a series of practical tests on functions of different dimensions, and manually encode the most efficient results into `evaluator.py`.\n\n### Visualization\nWe are planning on creating a visualization tool with `MatPlotLib` that can plot the computational graph (calculated in Reverse Mode) of simple functions that are being differentiated. Obviously, the computational graph of very complex functions with many different inputs and outputs can be impractical to represent on a screen, so one of the biggest challenges that we will face is to have our program able determine when it can produce a visual tool that can be easily rendered, and when it cannot. \n\n## Impact\n\n\n## Future\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "Adifpy", "package_url": "https://pypi.org/project/Adifpy/", "platform": null, "project_url": "https://pypi.org/project/Adifpy/", "project_urls": { "Homepage": "https://code.harvard.edu/CS107/team33" }, "release_url": "https://pypi.org/project/Adifpy/0.0.3/", "requires_dist": [ "matplotlib~=3.6.1", "numpy~=1.23.4" ], "requires_python": ">=3.10", "summary": "Perform automatic differentiation", "version": "0.0.3", "yanked": false, "yanked_reason": null }, "last_serial": 16022303, "releases": { "0.0.2": [ { "comment_text": "", "digests": { "md5": "2a1e446d0705a196a92c31e155bc62f9", "sha256": "ede9f07af39a7d49b572f62e411ca385b27bc3752dda40883a4e8534631de197" }, "downloads": -1, "filename": "adifpy-0.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "2a1e446d0705a196a92c31e155bc62f9", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.10", "size": 100378, "upload_time": "2022-12-07T16:09:16", "upload_time_iso_8601": "2022-12-07T16:09:16.775210Z", "url": "https://files.pythonhosted.org/packages/42/56/68d1387c6291aa742953d29af9be6785cda7264d72b3709ed92158e6f194/adifpy-0.0.2-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "d0f992742e6ae472494ab74aefc552fd", "sha256": "7ac60fdd4b4c036a01d0d3b38802920c0e1e53428bce684a37ab80fa5b400b0e" }, "downloads": -1, "filename": "adifpy-0.0.2.tar.gz", "has_sig": false, "md5_digest": "d0f992742e6ae472494ab74aefc552fd", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.10", "size": 2016873, "upload_time": "2022-12-07T16:09:18", "upload_time_iso_8601": "2022-12-07T16:09:18.259073Z", "url": "https://files.pythonhosted.org/packages/f4/d0/43a2a52966f791ca23d8636866e584c8bd2221ab54ffcc7f53e0267b5702/adifpy-0.0.2.tar.gz", "yanked": false, "yanked_reason": null } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "673782dffa593932ee697bdd3e76e6df", "sha256": "b49bba56acbeadbdb5210164a6824889428167178417b29e26af0cd62df3f2d2" }, "downloads": -1, "filename": "adifpy-0.0.3-py3-none-any.whl", "has_sig": false, "md5_digest": "673782dffa593932ee697bdd3e76e6df", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.10", "size": 105426, "upload_time": "2022-12-07T16:16:00", "upload_time_iso_8601": "2022-12-07T16:16:00.439931Z", "url": "https://files.pythonhosted.org/packages/19/5a/f4bc18f17dc52a4d3c4ad14b2d5f366332e6054d42e8d5a0853e21980529/adifpy-0.0.3-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "34c31b808653f2f2956592c9f203e3c5", "sha256": "66852c7d4902b826ac46b1a76b342360d2f8e8195de0920c61f2056f082ae666" }, "downloads": -1, "filename": "adifpy-0.0.3.tar.gz", "has_sig": false, "md5_digest": "34c31b808653f2f2956592c9f203e3c5", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.10", "size": 2022198, "upload_time": "2022-12-07T16:16:02", "upload_time_iso_8601": "2022-12-07T16:16:02.908604Z", "url": "https://files.pythonhosted.org/packages/fa/a1/4cdcada4cb200dc3d2f57926801180477667c24f33311b8b246a2f74df4f/adifpy-0.0.3.tar.gz", "yanked": false, "yanked_reason": null } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "673782dffa593932ee697bdd3e76e6df", "sha256": "b49bba56acbeadbdb5210164a6824889428167178417b29e26af0cd62df3f2d2" }, "downloads": -1, "filename": "adifpy-0.0.3-py3-none-any.whl", "has_sig": false, "md5_digest": "673782dffa593932ee697bdd3e76e6df", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.10", "size": 105426, "upload_time": "2022-12-07T16:16:00", "upload_time_iso_8601": "2022-12-07T16:16:00.439931Z", "url": "https://files.pythonhosted.org/packages/19/5a/f4bc18f17dc52a4d3c4ad14b2d5f366332e6054d42e8d5a0853e21980529/adifpy-0.0.3-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "34c31b808653f2f2956592c9f203e3c5", "sha256": "66852c7d4902b826ac46b1a76b342360d2f8e8195de0920c61f2056f082ae666" }, "downloads": -1, "filename": "adifpy-0.0.3.tar.gz", "has_sig": false, "md5_digest": "34c31b808653f2f2956592c9f203e3c5", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.10", "size": 2022198, "upload_time": "2022-12-07T16:16:02", "upload_time_iso_8601": "2022-12-07T16:16:02.908604Z", "url": "https://files.pythonhosted.org/packages/fa/a1/4cdcada4cb200dc3d2f57926801180477667c24f33311b8b246a2f74df4f/adifpy-0.0.3.tar.gz", "yanked": false, "yanked_reason": null } ], "vulnerabilities": [] }