core
Tutorial 01: Core module utilities#
Author:
Mauricio Aristizabal Cano
University of Texas at San Antonio
Universidad EAFIT
Date of creation: Jun 19 2024
Last modification: Aug 12 2024
Introduction#
This document showcases and describes some of the capabilities of the core module in the pyoti
library.
The reasoning behind the core module is to provide a unique module that contains the core functionality and classes that that are common to the different implementations of the OTI numbers (sparse, dense, etc), centralizing some of the calculations. Some of the capabilities are:
Manipulation of imaginary direcitions (definition, multiplication/division between directions, etc)
Translation layer between precomputed data and the OTI number implementation
Utility functions, e.g. getting total number of directions for m basis and n order, translating human-readable notation to faster imaginary direction indexing, etc.
Inquiring the capabilities of the implementation.
many others
In this document some of these capabilities will be shown.
Start by importing the module using the following code:
[1]:
import pyoti.core as coti # Import the core library module.
Basics: inquiring library capabilities.#
An important information is to know what is the maximum number of bases and maximum truncation order supported by the precomputed data. This information is accessed via the function print_capabilities()
. This function returns a string with the maximum number of basis that can be operated for a given order.
The default version of the library can compute derivatives of up-to 150th order, which are limited as follows:
[2]:
coti.print_capabilities()
Order Nbases Ndir
1 65000 65000
2 1000 500500
3 100 171700
4 100 4421275
5 10 2002
6 10 5005
7 10 11440
8 10 24310
9 10 48620
10 10 92378
11 5 1365
12 5 1820
13 5 2380
14 5 3060
15 5 3876
16 5 4845
17 5 5985
18 5 7315
19 5 8855
20 5 10626
21 3 253
22 3 276
23 3 300
24 3 325
25 3 351
26 3 378
27 3 406
28 3 435
29 3 465
30 3 496
31 3 528
32 3 561
33 3 595
34 3 630
35 3 666
36 3 703
37 3 741
38 3 780
39 3 820
40 3 861
41 3 903
42 3 946
43 3 990
44 3 1035
45 3 1081
46 3 1128
47 3 1176
48 3 1225
49 3 1275
50 3 1326
51 2 52
52 2 53
53 2 54
54 2 55
55 2 56
56 2 57
57 2 58
58 2 59
59 2 60
60 2 61
61 2 62
62 2 63
63 2 64
64 2 65
65 2 66
66 2 67
67 2 68
68 2 69
69 2 70
70 2 71
71 2 72
72 2 73
73 2 74
74 2 75
75 2 76
76 2 77
77 2 78
78 2 79
79 2 80
80 2 81
81 2 82
82 2 83
83 2 84
84 2 85
85 2 86
86 2 87
87 2 88
88 2 89
89 2 90
90 2 91
91 2 92
92 2 93
93 2 94
94 2 95
95 2 96
96 2 97
97 2 98
98 2 99
99 2 100
100 2 101
101 2 102
102 2 103
103 2 104
104 2 105
105 2 106
106 2 107
107 2 108
108 2 109
109 2 110
110 2 111
111 2 112
112 2 113
113 2 114
114 2 115
115 2 116
116 2 117
117 2 118
118 2 119
119 2 120
120 2 121
121 2 122
122 2 123
123 2 124
124 2 125
125 2 126
126 2 127
127 2 128
128 2 129
129 2 130
130 2 131
131 2 132
132 2 133
133 2 134
134 2 135
135 2 136
136 2 137
137 2 138
138 2 139
139 2 140
140 2 141
141 2 142
142 2 143
143 2 144
144 2 145
145 2 146
146 2 147
147 2 148
148 2 149
149 2 150
150 2 151
From this output, notice that for first order directions, upto 65000 independent basis can be used. In contrast, for third order this number is reduced to 100. This number depends on the pre-computed data information, which can be re-generated using the otigen executable (see compilation instructions).
Another inquiry utility is the get_trunc_order()
function, which returns the maximum truncation order of the implementation that is currently running.
[3]:
coti.get_trunc_order()
[3]:
150
Getting number of imaginary directions.#
A fully dense OTI number with truncation order \(n\) and a number of basis \(m\) will have a total of \(n_{\mbox{total}}\) imaginary directions (including the real direction). This number is given by:
This number can be efficiently evaluated using the function ndir_total( nbasis, order )
. For instance, for a total of \(m=2\) basis and truncation order \(n=3\), the number of imaginary directions is 10. For an OTI number \(a^*\), with \(a^*\in \mathbb{OTI}^{n=2}_{m=3}\), it is as follows:
[4]:
n_total = coti.ndir_total(2,3)
n_total
[4]:
10
The total number of directions with a specific order \(p\), \(n_{p}\) for a number of basis \(m\) is given by
This number can be efficiently evaluated using the function ndir_order( nbasis, order_p )
. For the prevous case, we have that
[5]:
n_1 = coti.ndir_order(2,1)
n_2 = coti.ndir_order(2,2)
n_3 = coti.ndir_order(2,3)
print(f"n_1 = {n_1}, n_2 = {n_2}, n_3 = {n_3} ")
n_1 = 2, n_2 = 3, n_3 = 4
Direction helper#
The direction helper is an object of class dHelp, that loads and manipulates the precomputed data to speedup calculations. This object loads a particular set of precomputed data helper for each order of imaignary directions. Therefore, if the maximum truncation order is 150, the number of interal helpers in dHelp will be 150.
Here are described some uses of the direction helper.
Getting the direction helper#
Use the function get_dHelp()
to get the direction helper object.
[6]:
dh = coti.get_dHelp()
dh
[6]:
<dhelp with 150 helpers loaded>
[7]:
# You can also use print(dh). Here, only a portion of the output is displayed.
print(str(dh)[:490])
Direction Helper object with 150 helpers:
Helper 1:
- Truncation order: 1
- Number of imaginary bases: 65000
- Total number of imaginary directions: 65000
- Total number of multiplication tables: 0
Helper 2:
- Truncation order: 2
- Number of imaginary bases: 1000
- Total number of imaginary directions: 500500
- Total number of multiplication tables: 1
Helper 3:
- Truncation order: 3
- Number of imaginary bases: 100
- Total number of imaginary directions: 171700
Extracting the full directions given an index-order pair:#
The full direction of a “index,order” pair can be obtained from the precomputed data. For this, give the index, order pair and the direction helper will return the corresponding
Index \(\rightarrow\) |
\(0\) |
\(1\) |
\(2\) |
\(3\) |
\(4\) |
\(5\) |
\(6\) |
\(7\) |
\(8\) |
\(9\) |
… |
---|---|---|---|---|---|---|---|---|---|---|---|
Order \(1\) |
\(\epsilon_{1}\) |
\(\epsilon_{2}\) |
\(\epsilon_{3}\) |
\(\epsilon_{4}\) |
\(\epsilon_{5}\) |
\(\epsilon_{6}\) |
\(\epsilon_{7}\) |
\(\epsilon_{8}\) |
\(\epsilon_{9}\) |
\(\epsilon_{10}\) |
… |
Order \(2\) |
\(\epsilon_{1}^{2}\) |
\(\epsilon_{1}\epsilon_{2}\) |
\(\epsilon_{2}^{2}\) |
\(\epsilon_{1}\epsilon_{3}\) |
\(\epsilon_{2}\epsilon_{3}\) |
\(\epsilon_{3}^{2}\) |
\(\epsilon_{1}\epsilon_{4}\) |
\(\epsilon_{2}\epsilon_{4}\) |
\(\epsilon_{3}\epsilon_{4}\) |
\(\epsilon_{4}^{2}\) |
… |
Order \(3\) |
\(\epsilon_{1}^{3}\) |
\(\epsilon_{1}^{2}\epsilon_{2}\) |
\(\epsilon_{1}\epsilon_{2}^{2}\) |
\(\epsilon_{2}^{3}\) |
\(\epsilon_{1}^{2}\epsilon_{3}\) |
\(\epsilon_{1}\epsilon_{2}\epsilon_{3}\) |
\(\epsilon_{2}^{2}\epsilon_{3}\) |
\(\epsilon_{1}\epsilon_{3}^{2}\) |
\(\epsilon_{2}\epsilon_{3}^{2}\) |
\(\epsilon_{3}^{3}\) |
… |
Order \(4\) |
\(\epsilon_{1}^{4}\) |
\(\epsilon_{1}^{3}\epsilon_{2}\) |
\(\epsilon_{1}^{2}\epsilon_{2}^{2}\) |
\(\epsilon_{1}\epsilon_{2}^{3}\) |
\(\epsilon_{2}^{4}\) |
\(\epsilon_{1}^{3}\epsilon_{3}\) |
\(\epsilon_{1}^{2}\epsilon_{2}\epsilon_{3}\) |
\(\epsilon_{1}\epsilon_{2}^{2}\epsilon_{3}\) |
\(\epsilon_{2}^{3}\epsilon_{3}\) |
\(\epsilon_{1}^{2}\epsilon_{3}^{2}\) |
… |
Order \(5\) |
\(\epsilon_{1}^{5}\) |
\(\epsilon_{1}^{4}\epsilon_{2}\) |
\(\epsilon_{1}^{3}\epsilon_{2}^{2}\) |
\(\epsilon_{1}^{2}\epsilon_{2}^{3}\) |
\(\epsilon_{1}\epsilon_{2}^{4}\) |
\(\epsilon_{2}^{5}\) |
\(\epsilon_{1}^{4}\epsilon_{3}\) |
\(\epsilon_{1}^{3}\epsilon_{2}\epsilon_{3}\) |
\(\epsilon_{1}^{2}\epsilon_{2}^{2}\epsilon_{3}\) |
\(\epsilon_{1}\epsilon_{2}^{3}\epsilon_{3}\) |
… |
Order \(6\) |
\(\epsilon_{1}^{6}\) |
\(\epsilon_{1}^{5}\epsilon_{2}\) |
\(\epsilon_{1}^{4}\epsilon_{2}^{2}\) |
\(\epsilon_{1}^{3}\epsilon_{2}^{3}\) |
\(\epsilon_{1}^{2}\epsilon_{2}^{4}\) |
\(\epsilon_{1}\epsilon_{2}^{5}\) |
\(\epsilon_{2}^{6}\) |
\(\epsilon_{1}^{5}\epsilon_{3}\) |
\(\epsilon_{1}^{4}\epsilon_{2}\epsilon_{3}\) |
\(\epsilon_{1}^{3}\epsilon_{2}^{2}\epsilon_{3}\) |
… |
Order \(7\) |
\(\epsilon_{1}^{7}\) |
\(\epsilon_{1}^{6}\epsilon_{2}\) |
\(\epsilon_{1}^{5}\epsilon_{2}^{2}\) |
\(\epsilon_{1}^{4}\epsilon_{2}^{3}\) |
\(\epsilon_{1}^{3}\epsilon_{2}^{4}\) |
\(\epsilon_{1}^{2}\epsilon_{2}^{5}\) |
\(\epsilon_{1}\epsilon_{2}^{6}\) |
\(\epsilon_{2}^{7}\) |
\(\epsilon_{1}^{6}\epsilon_{3}\) |
\(\epsilon_{1}^{5}\epsilon_{2}\epsilon_{3}\) |
… |
Order \(8\) |
\(\epsilon_{1}^{8}\) |
\(\epsilon_{1}^{7}\epsilon_{2}\) |
\(\epsilon_{1}^{6}\epsilon_{2}^{2}\) |
\(\epsilon_{1}^{5}\epsilon_{2}^{3}\) |
\(\epsilon_{1}^{4}\epsilon_{2}^{4}\) |
\(\epsilon_{1}^{3}\epsilon_{2}^{5}\) |
\(\epsilon_{1}^{2}\epsilon_{2}^{6}\) |
\(\epsilon_{1}\epsilon_{2}^{7}\) |
\(\epsilon_{2}^{8}\) |
\(\epsilon_{1}^{7}\epsilon_{3}\) |
… |
Order \(9\) |
\(\epsilon_{1}^{9}\) |
\(\epsilon_{1}^{8}\epsilon_{2}\) |
\(\epsilon_{1}^{7}\epsilon_{2}^{2}\) |
\(\epsilon_{1}^{6}\epsilon_{2}^{3}\) |
\(\epsilon_{1}^{5}\epsilon_{2}^{4}\) |
\(\epsilon_{1}^{4}\epsilon_{2}^{5}\) |
\(\epsilon_{1}^{3}\epsilon_{2}^{6}\) |
\(\epsilon_{1}^{2}\epsilon_{2}^{7}\) |
\(\epsilon_{1}\epsilon_{2}^{8}\) |
\(\epsilon_{2}^{9}\) |
… |
From the Table above, the index-order pair <9, 5> (index 9, order 5) corresponds to the direction \(\epsilon_{1}\epsilon_{2}^{3}\epsilon_{3}\). Therefore, its compact dirArray
representation is [1,[2,3],3]
. This can be easily retrieved from the direction helper object using the get_compact_fulldir(indx,order)
method:
[8]:
indx = 9
order = 5
dh.get_compact_fulldir(indx, order)
[8]:
[1, [2, 3], 3]
The expanded full dir can be obtained using the get_fulldir(indx,order)
method
[9]:
dh.get_fulldir(indx, order)
[9]:
array([1, 2, 2, 2, 3], dtype=uint16)
Imaginary directions can become extensive. An ideal representation is by defining its bases and exponents. This information is also efficiently provided by the direction helper. Use dh.get_base(indx, order)
[10]:
bases,exponents = dh.get_base_exp(indx,order)
print(f'Bases {bases}, exponents {exponents}')
Bases [1 2 3], exponents [1 3 1]
Converting imaginary direction notations#
The direction helper provides an easy way to converting from index-order pairs to human readable compact and full direction expressions. However, the inverse operation is not trivial.
Converting from index-order to human readable dirArray#
Use the direction helper’s get_compact_fulldir(indx, order)
or get_fulldir(indx, order)
methods:
[11]:
indx = 9
order = 5
dh.get_compact_fulldir(indx, order)
[11]:
[1, [2, 3], 3]
Converting from human readable dirArray to index-order#
Use the function imdir(dirArray)
in the core module (this is not a method in the direction helper). This function uses the dirArray
input and returns a 2-element list with the corresponding index-order pair of this direction.
[12]:
coti.imdir([1,[2,3],3])
[12]:
[9, 5]
Operating Imaginary directions#
Imagianry directions can be multiplied and divided. Here are some functions in the core pyoti module that can provide this functionality.
Multiplying imaginary directions#
Imaginary directions can be multiplied using the direction helper and the method mult_dir(idx1,ord1,idx2,ord2)
. The first two inputs corrspond to the index order pairs of the first imatginary direction, and the last two inputs correspond to the second imaginary direction. The result is the index-order pair of the resulting imaginary direction.
For example multiplying \(\epsilon_{1}^{3} \times \left( \epsilon_{2}^2\epsilon_{3}\right)\) is \((0,3) \times (6,3)\) is the direction \(\epsilon_{1}^{3}\epsilon_{2}^{2}\epsilon_{3}\) which corresponds to the \((9,6)\) direction.
[13]:
# Multipying two
ridx,rord = dh.mult_dir( 0,3, 6,3 )
print(f'Result index: {ridx} order {rord} --> {dh.get_compact_fulldir(ridx, rord)}')
Result index: 9 order 6 --> [[1, 3], [2, 2], 3]
Dividing imaginary directions#
At first glance, the user might think that this is a useless operation. However, division of two imaginary directions is necessary to determine whether a direction is multiple of another direction.
Let’s consider the following example: Divide the direction \(\epsilon_{1}^{3}\epsilon_{2}^{2}\epsilon_{3}\) (9,6) by \(\epsilon_{1}^{2}\epsilon_{2}\) (1,3). The result is \(\epsilon_{1}\epsilon_{2}\epsilon_{3}\).
This operation can be performed using the functions div_imdir_idxord(num_idx,num_ord, den_idx,den_ord)
where num_idx,num_ord
are the index-order pairs of the numerator direction, and den_idx,den_ord
are index-order pair of the denominator direction. The result is a 2-element list with the index-order pair of the resulting direction.
[14]:
coti.div_imdir_idxord(9,6, 1,3)
[14]:
[5, 3]
The same operation can be achieved but in human readable form using the function div_imdir(numDirArr,denDirArr)
where numDirArr
is the dirArray of the numerator direction, and denDirArr
is the dirArray of the denominator direction. For the same example, the result is the dirArray of the resulting direction.
[15]:
coti.div_imdir([[1,3],[2,2],3], [[1,2],2])
[15]:
[1, 2, 3]
In the case the division of the imaginary directions is not possible, both functions return an integer -1. For example, dividing \(\epsilon_{1}^{3}\) by \(\epsilon_{2}\) is not possible, and therefore the result is
[16]:
coti.div_imdir([[1,3]], [2])
[16]:
-1