{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## `static` Tutorial 01: Basic usage.\n", "\n", "```\n", "Contributors:\n", " Mauricio Aristizabal\n", " University of Texas at San Antonio\n", " Universidad EAFIT, Colombia.\n", "\n", "Date of creation: Jun 20 2024\n", "Last modification: Jun 20 2024\n", "```\n", "\n", "### Introduction\n", "\n", "The main purpose of this document is to show the basic operation of the static 'Order truncated Imaginary' (OTI) module in ```pyoti``` . Multiple examples are given in order to show and understand the capabilities of the library, as well as its basic advantages.\n", "\n", "The sparse implementation provides a robust and dynamic interface to the OTI numbers. However, a significant performance gain can be obtained when using the static library implementation. That is the reasoning behind supporting a static OTI implementation. The word 'static' is used in connotation of two things:\n", "\n", "- Static memory usage: The amount of memory used is fixed and no allocations are done to the \n", "- Static algebra: OTIs have the capability to change truncation order and number of basis according to the needed applications. A static implementation means that the number of basis and truncation order is fixed.\n", "\n", "#### Importing the library\n", "The static module is composed of multiple submodules that suport a specific combination of OTI numbers with number of imaginary basis $m$ and truncation order $n$. To get that specific version of the library, you must load the version with the name ```onummn```. For example, plain dual numbers which is the equivalent of an OTI with $m=1$ and truncation order $n=1$, you should load the static submodule ```onumm1n1```. This will allow the computations of first order derivatives with respect to a single variable, with $a^*\\in \\mathbb{OTI}^1_1$:\n", "\n", "$$\n", "a^*=a_r+\n", "a_{\\epsilon_1} \\epsilon_1\n", "$$\n", "\n", "Similarly, you can import a module for the static support of OTis with $m=2$ and $n=3$, i.e. $a^*\\in \\mathbb{OTI}^3_2$ where \n", "\n", "$$\n", "a^*=a_r+\n", "a_{\\epsilon_1} \\epsilon_1 + a_{\\epsilon_2} \\epsilon_2 + \n", "a_{\\epsilon_1^2} \\epsilon_1^2 + a_{\\epsilon_1\\epsilon_2} \\epsilon_1\\epsilon_2 + a_{\\epsilon_2^2} \\epsilon_2^2 +\n", "a_{\\epsilon_1^3} \\epsilon_1^3 + a_{\\epsilon_1^2\\epsilon_2} \\epsilon_1^2\\epsilon_2 + \n", "a_{\\epsilon_1\\epsilon_2^2} \\epsilon_1\\epsilon_2^2 + a_{\\epsilon_2^3} \\epsilon_2^3 \n", "$$\n", "\n", "The contents of ```static``` module mimic most of the functions on the ```sparse``` module. It is recommended the reader also looks at the tutorials of the ```sparse``` implementation.\n", "\n", "The static implementation of the library in the current version of pyoti comes with support for\n", "\n", "| m/n | 1 | 2 | 3 | 4 | \n", "| :---: | :---: | :---: | :---: | :---: |\n", "| 1 | ```onumm1n1``` | ```onumm1n2``` | ```onumm1n3``` | ```onumm1n4``` |\n", "| 2 | ```onumm2n1``` | ```onumm2n2``` | ```onumm2n3``` | ```onumm2n4``` |\n", "| 3 | ```onumm3n1``` | ```onumm3n2``` | ```onumm3n3``` | ```onumm3n4``` |\n", "\n", "To print the available static modules, use the following" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on package pyoti.static in pyoti:\n", "\n", "NAME\n", " pyoti.static\n", "\n", "PACKAGE CONTENTS\n", " onumm10n1\n", " onumm10n2\n", " onumm1n1\n", " onumm1n10\n", " onumm1n2\n", " onumm1n20\n", " onumm1n3\n", " onumm1n30\n", " onumm1n4\n", " onumm2n1\n", " onumm2n2\n", " onumm2n3\n", " onumm2n4\n", " onumm3n1\n", " onumm3n2\n", " onumm3n3\n", " onumm3n4\n", "\n", "FILE\n", " (built-in)\n", "\n", "\n" ] } ], "source": [ "help('pyoti.static')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For the this tutorial, $m=2$ and $n=2$ will be used, $a^*\\in \\mathbb{OTI}^2_2$ where \n", "\n", "$$\n", "a^*=a_r+\n", "a_{\\epsilon_1} \\epsilon_1 + a_{\\epsilon_2} \\epsilon_2 + \n", "a_{\\epsilon_1^2} \\epsilon_1^2 + a_{\\epsilon_1\\epsilon_2} \\epsilon_1\\epsilon_2 + a_{\\epsilon_2^2} \\epsilon_2^2\n", "$$\n", "\n", "thus the module ```onumm2n2``` is imported using:\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [], "source": [ "import pyoti.static.onumm2n2 as oti # Import the static library for m=2 and n=3. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Basics\n", "#### Scalar creation\n", "\n", "In order to create an OTI scalar, the easiest way to do so is as follows" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10.0000 + 1.0000 * e([1]) + 0.0000 * e([2]) + 0.0000 * e([[1,2]]) + 5.2000 * e([1,2]) + 3.0000 * e([[2,2]])\n" ] } ], "source": [ "a = 10 + oti.e(1) + 5.2* oti.e([1,2]) + 3*oti.e([2,2])\n", "print(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The OTI number ```a``` has been created. This number represents the following:\n", "\n", "$$\n", "a = 10 + \\epsilon_1 + 5.2 \\ \\epsilon_1\\epsilon_2 + 3 \\ \\epsilon_2^2\n", "$$\n", "\n", "The 'direction array' given as a parameter to the function ```oti.e(dirArray)``` is the same as the one used in the ```sparse``` module, and can be defined as follows:\n", "\n", "- ```oti.e(i)``` :$\\epsilon_i$ (unsigned integer)\n", "\n", "- ```oti.e([i,j,k,l])``` : $\\epsilon_i\\epsilon_j\\epsilon_k\\epsilon_l$ (plain list of integers, with $n$ elements)\n", "\n", "- ```oti.e([[i,m],j,[k,n],l])``` : $\\epsilon_i^m\\epsilon_j\\epsilon_k^n\\epsilon_l$ (the items with exponents other than 1, are defined as a 2-element list representing the base and exponent pair. Usually $m$, $n$ are greater than 1, but can be any integer $m,n\\geq0$)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note: the ```e(dirArray)``` function supports the same inputs as the sparse function. However, it will always create a static number (meaning all its directions will be created and will be zero). Therefore, there is no need to assign the truncation order for static implementations, as it is defined in the module itself.\n", "\n", "Additionally, creating directions that are outside of the truncation conditions of the static library selected, the result will be an OTI number filled with zeros.\n", "\n", "For example" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.0000 + 0.0000 * e([1]) + 0.0000 * e([2]) + 0.0000 * e([[1,2]]) + 0.0000 * e([1,2]) + 0.0000 * e([[2,2]])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "oti.e([1,1,1]) # Notice this is third order, but the module\n", " # has truncation order 2." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The number has been created with the same truncation order of the moudle. This can be requested from the object with the atribute ```order```" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "print(a.order)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Some users may be interested in knowing specific information about the ```onumm2n2``` object. ```pyoti``` provides a ```short_repr()``` method to show basic information about the object. It shows the real coefficient, the total number of non-zero imaginary coefficients and its truncation order." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "onumm2n2(10.0, nnz: 3, order: 2)\n" ] } ], "source": [ "print(a.short_repr())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Usign OTI numbers.\n", "\n", "In ```pyoti```, OTI numbers have overloaded operators so that operations work as similar as other scalar types in Python, like ```float``` or ```int```. The followign provides a summary of arithmetic operations supported in ```pyoti```:\n", "\n", "| Operation | Symbol | \n", "|---:|:---:|\n", "| Addition | ```a+b``` |\n", "| Subtraction | ```a-b``` |\n", "| Multiplication | ```a*b``` |\n", "| Division | ```a/b``` |\n", "| Power | ```a**b``` |\n", "\n", "\n", "#### Arithmetic operations\n", "\n", "##### Addition\n", "\n", "Add two OTIs using the ```+``` operator. For example, adding the numbers \n", "\n", "$$\n", "\\begin{array}{rcl}\n", "a &=& 10 + \\phantom{2.5 } \\epsilon_1 + 3 \\ \\epsilon_1\\epsilon_2+5.2 \\ \\epsilon_2^2 \\\\\n", "b &=& 10 + 2.5 \\ \\epsilon_1 - 3 \\ \\epsilon_1\\epsilon_2 \\phantom{+3 \\ \\epsilon_2^2} \\\\\n", "a+b &=& 20 + 3.5 \\ \\epsilon_1 +0 \\ \\epsilon_1\\epsilon_2 + 5.2 \\ \\epsilon_2^2\n", "\\end{array}\n", "$$" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The result is: 20.0000 + 3.5000 * e([1]) + 0.0000 * e([2]) + 0.0000 * e([[1,2]]) + 0.0000 * e([1,2]) + 5.2000 * e([[2,2]])\n" ] } ], "source": [ "a = 10.0 + oti.e([1]) + 3.0*oti.e([1,2]) + 5.2*oti.e([2,2]) \n", "b = 10.0 + 2.5*oti.e([1]) - 3.0*oti.e([1,2])\n", "\n", "sum_result = a+b\n", "print('The result is:', sum_result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Subtraction\n", "\n", "To subtract two OTI numbers, use ```-``` operator:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The result of a-b: 0.0000 - 1.5000 * e([1]) + 0.0000 * e([2]) + 0.0000 * e([[1,2]]) + 6.0000 * e([1,2]) + 5.2000 * e([[2,2]])\n", "Order of a-b: 2\n" ] } ], "source": [ "sub_result = a-b\n", "\n", "print(\"The result of a-b:\",sub_result)\n", "print(\"Order of a-b: \",sub_result.order)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Multiplication\n", "\n", "In order to Multiply two OTI numbers in the library, just use the ```*``` operator:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The result of a*b:\n", "100.0000 + 35.0000 * e([1]) + 0.0000 * e([2]) + 2.5000 * e([[1,2]]) + 0.0000 * e([1,2]) + 52.0000 * e([[2,2]])\n" ] } ], "source": [ "mult_result = a*b\n", "print(\"The result of a*b:\")\n", "print(mult_result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Division\n", "\n", "Division between two OTIs is performed as follows:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The result of a/b:\n", "1.0000 - 0.1500 * e([1]) + 0.0000 * e([2]) + 0.0375 * e([[1,2]]) + 0.6000 * e([1,2]) + 0.5200 * e([[2,2]])\n" ] } ], "source": [ "div_result = a/b\n", "print(\"The result of a/b:\")\n", "print(div_result) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Elementary functions\n", "\n", "```pyoti``` supports multiple functions, such as sine, cosine, etc. All functions are accessed from the module. For instance, using the imported ```pyoti.sparse``` module as ```oti```, functions can be called using e.g. ```oti.sin()```. Supported functions are given in the following table: \n", "\n", "Trigonometric functions\n", "\n", "| Function | Symbol | \n", "|---:|:---:|\n", "| Sine | ```sin(x)``` |\n", "| Cosine | ```cos(x)``` |\n", "| Tangent | ```tan(x)``` |\n", "| Arcsine | ```asin(x)``` |\n", "| Arccosine | ```acos(x)``` |\n", "| Arctangent | ```atan(x)``` |\n", "\n", "\n", "Hyperbolic functions\n", "\n", "| Function | Symbol | \n", "|---:|:---:|\n", "| Hyperbolic sine | ```sinh(x)``` |\n", "| Hyperbolic cosine | ```cosh(x)``` |\n", "| Hyperbolic tangent | ```tanh(x)``` |\n", "| Inverse hyperbolic sine | ```asinh(x)``` |\n", "| Inverse hyperbolic cosine | ```acosh(x)``` |\n", "| Inverse hyperbolic tangent | ```atanh(x)``` |\n", "\n", "\n", "\n", "Logarithmic/Exponential functions\n", "\n", "| Function | Symbol | \n", "|---:|:---:|\n", "| Exponential | ```exp(x)``` |\n", "| Natural logarithm | ```log(x)``` |\n", "| Logarithm base 10 | ```log10(x)``` |\n", "| Logarithm base b | ```logb(x,b)``` |\n", "\n", "\n", "\n", "Power functions\n", "\n", "| Function | Symbol | \n", "|---:|:---:|\n", "| Power | ```pow(x,e)``` or ```x**e``` |\n", "| Square root | ```sqrt(x)``` |\n", "| Cubic root | ```cbrt(x)``` |\n", "\n" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.1411 - 1.9800 * e([1]) - 0.0000 * e([2]) - 0.2822 * e([[1,2]]) - 0.0000 * e([1,2]) + 4.2570 * e([[2,2]])\n" ] } ], "source": [ "x = 3 + 2*oti.e([1]) - 4.3 * oti.e([2,2])\n", "f = oti.sin(x)\n", "print(f)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Get and set coefficients of an OTI number.\n", "\n", "To get an imaginary coefficient, use the method '```get_im(dirArray)```' and place the direction array of the direction you want" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10.0000 + 1.0000 * e([1]) + 0.0000 * e([2]) + 0.0000 * e([[1,2]]) + 3.0000 * e([1,2]) + 5.2000 * e([[2,2]])\n" ] } ], "source": [ "a = 10.0 + oti.e([1]) + 3.0*oti.e([1,2]) + 5.2*oti.e([2,2]) \n", "print(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To get the coefficient of the direction $\\epsilon_1$ use:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.0\n" ] } ], "source": [ "print(a.get_im(1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.0\n" ] } ], "source": [ "print(a.get_im([1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To get the coefficient of $\\epsilon_2^2$ use:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5.2\n" ] } ], "source": [ "print(a.get_im([[2,2]]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5.2\n" ] } ], "source": [ "print(a.get_im([2,2]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Coefficients that are not explicitly shown in the number are zero. For example, the coefficient along direction $\\epsilon_1\\epsilon_2^2$ is:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.0\n" ] } ], "source": [ "print(a.get_im([1,2,2]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are two ways to get the real coefficient: Get the real " ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10.0\n" ] } ], "source": [ "print(a.get_im(0))" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10.0" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a.real" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Set coefficients of an OTI number \n", "\n", "Similar to the methods of getting coefficients, setting the values of coefficients works in the same way. If the coefficient already exist, it will be modified, and if the coefficient does not exist, it will be created. Use the method ```set_im(newValue,dirArray)``` to do so.\n" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10.0000 + 1.0000 * e([1]) + 0.0000 * e([2]) + 0.0000 * e([[1,2]]) + 3.0000 * e([1,2]) + 5.2000 * e([[2,2]])\n" ] } ], "source": [ "a = 10.0 + oti.e([1]) + 3.0*oti.e([1,2]) + 5.2*oti.e([2,2]) \n", "print(a)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10.0000 + 1.0000 * e([1]) + 0.0000 * e([2]) + 7.3000 * e([[1,2]]) + 3.0000 * e([1,2]) + 5.2000 * e([[2,2]])\n" ] } ], "source": [ "a.set_im(7.3,[1,1])\n", "print(a)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "jupyter": { "outputs_hidden": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10.0000 + 1.0000 * e([1]) + 4.2000 * e([2]) + 7.3000 * e([[1,2]]) + 3.0000 * e([1,2]) + 5.2000 * e([[2,2]])\n" ] } ], "source": [ "a.set_im(4.2,[2])\n", "print(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Alternative way to get and set components.\n", "\n", "There is an alternative way to get and set the imaginary coefficients within pyoti. This approach uses the \"index, order\" pairs of an imagianry direction, instead of the \"direction array\" components.\n", "\n", "The index-order pair is the index according to the sorted imaginary directions of an OTI number. For a given order, the index is related to the position of the direction. For first order it is trivial, as the index corresponds to the basis of the imaginary direction (minus one). for Higher order, the index is determined by the appearance of the new imaginary basis. For instance:\n", "\n", "For order 2\n", "\n", "| dirArray | Index | order |\n", "|:--------:|:-----:|:------|\n", "|`[1,1]` | 0 | 2 |\n", "|`[1,2]` | 1 | 2 |\n", "|`[2,2]` | 2 | 2 |\n", "|`[1,3]` | 3 | 2 |\n", "|`[2,3]` | 4 | 2 |\n", "|`[3,3]` | 5 | 2 |\n", "\n", "For order 3.\n", "\n", "| dirArray | Index | order |\n", "|:--------:|:-----:|:------|\n", "|`[1,1,1]` | 0 | 3 |\n", "|`[1,1,2]` | 1 | 3 |\n", "|`[1,2,2]` | 2 | 3 |\n", "|`[2,2,2]` | 3 | 3 |\n", "|`[1,1,3]` | 4 | 3 |\n", "|`[1,2,3]` | 5 | 3 |\n", "|`[2,2,3]` | 6 | 3 |\n", "|`[1,3,3]` | 7 | 3 |\n", "|`[2,3,3]` | 8 | 3 |\n", "|`[3,3,3]` | 9 | 3 |\n", "\n", "\n", "Index-order pairs are defined as a list of two elements, first the index (0-based) and then the order: `[idx,order]`." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3.0" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a[ [ 1, 2 ] ] # gets e([1,2]) coefficient" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "10.0000 + 1.0000 * e([1]) + 4.2000 * e([2]) + 7.3000 * e([[1,2]]) + 3.0000 * e([1,2]) + 55.0000 * e([[2,2]])" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a[ [ 2, 2 ] ] = 55 # Sets e([2,2]) coefficient\n", "a" ] } ], "metadata": { "kernelspec": { "display_name": "pyoti", "language": "python", "name": "pyoti" }, "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.9.13" } }, "nbformat": 4, "nbformat_minor": 4 }