Skip to content

Commit 8b3f2fa

Browse files
michaeleg21syurkevi
authored andcommitted
Initial documentation commit
initial python arrayfire documentation with overview and tutorial. This includes tests for all snippets used in the documentation.
1 parent b676b84 commit 8b3f2fa

25 files changed

+2126
-0
lines changed

dev-requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ pytest-cov>=5.0.0
1818
# Allows codecov to generate coverage reports
1919
coverage[toml]>=6.4
2020
codecov>=2.1.12
21+
22+
# Allows documentation
23+
sphinx>=7.3.7
24+
sphinxawesome_theme>=5.2.0

docs/__init__.py

Whitespace-only changes.

docs/_static/custom.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
a {
2+
color: blue; /* Set the color of links to blue */
3+
text-decoration: underline; /* Underline links */
4+
}
5+
6+
a:hover {
7+
color: darkblue; /* Change link color to dark blue on hover */
8+
}

docs/afjit.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# [jit-snippet]
2+
3+
# As JIT is automatically enabled in ArrayFire, this version of the function
4+
# forces each expression to be evaluated. If the eval() function calls are
5+
# removed, then the execution of this code would be equivalent to the
6+
# following function.
7+
8+
def pi_no_jit(x, y, temp, samples):
9+
temp = x * x
10+
temp.eval()
11+
temp += y * y
12+
temp.eval()
13+
temp = sqrt(temp)
14+
temp.eval()
15+
temp = temp < 1
16+
temp.eval()
17+
return 4.0 * sum(temp) / samples
18+
19+
def pi_jit(x, y, temp, samples):
20+
temp = sqrt(x * x + y * y) < 1
21+
temp.eval()
22+
return 4.0 * sum(temp) / samples
23+
24+
# [jit-endsnippet]

docs/arrayandmatrixmanipulation.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# [manipulation1-snippet]
2+
3+
import arrayfire as af
4+
5+
# Creates a 3x3 array filled with random numbers between [0, 1)
6+
a = af.randu((3, 3))
7+
8+
# Flattens the array 'a' into a 1-dimensional column vector
9+
flat_a = af.flat(a)
10+
11+
# Display the original array 'a'
12+
print(a)
13+
14+
# [manipulation1-endsnippet]
15+
16+
17+
# [manipulation2-snippet]
18+
19+
20+
import arrayfire as af
21+
22+
# Generate a 5x2 array of uniformly distributed random numbers between [0, 1)
23+
a = af.randu((5, 2))
24+
25+
# Print the original array 'a'
26+
print("Original array 'a' [5 2 1 1]")
27+
print(a)
28+
29+
# Flip the array 'a' along both axes (rows and columns)
30+
flip_a = af.flip(a)
31+
32+
# Print the flipped array 'flip_a'
33+
print("\nFlipped array 'flip_a' [5 2 1 1]")
34+
print(flip_a)
35+
36+
# [manipulation2-endsnippet]
37+
38+
39+
# [manipulation3-snippet]
40+
41+
import arrayfire as af
42+
43+
# Generate a 1-dimensional array 'a' of size 5 filled with uniformly distributed random numbers between [0, 1)
44+
a = af.randu((5,))
45+
46+
# Print the original array 'a'
47+
print("Original array 'a' [5 1 1 1]")
48+
print(a)
49+
50+
# Join the array 'a' with itself along axis 0
51+
a_join = af.join(0, a, a)
52+
53+
# Print the joined array 'a_join'
54+
print("\nJoined array 'a_join' [10 1 1 1]")
55+
print(a_join)
56+
# [manipulation3-endsnippet]
57+
58+
59+
# [manipulation4-snippet]
60+
61+
import arrayfire as af
62+
63+
a = af.randu((8,))
64+
65+
print(a)
66+
67+
moddims_a = af.moddims(a,(2,4))
68+
69+
print(moddims_a)
70+
71+
moddims_b = af.moddims(a,(len(a),))
72+
print(moddims_b)
73+
74+
# [manipulation4-endsnippet]
75+
76+
77+
# [manipulation5-snippet]
78+
79+
80+
import arrayfire as af
81+
82+
a = af.randu((2,2,3,1))
83+
84+
print(a)
85+
86+
a_reorder = af.reorder(a,())
87+
# [manipulation5-endsnippet]
88+
89+
# [manipulation6-snippet]
90+
91+
import arrayfire as af
92+
93+
a = af.randu((3,5))
94+
print(a)
95+
96+
a_shift = af.shift(a,(0,2))
97+
print(a_shift)
98+
99+
a_shift1 = af.shift(a,(-1,2))
100+
print(a_shift1)
101+
102+
# [manipulation6-endsnippet]
103+
104+
105+
# [manipulation7-snippet]
106+
107+
import arrayfire as af
108+
109+
a = af.randu((3,)) #[3,1,1,1]
110+
111+
print (a)
112+
113+
a_tile = af.tile(a,(2,))
114+
print(a_tile)
115+
116+
a_tile1 = af.tile(a,(2,2))
117+
print(a_tile1)
118+
119+
a_tile2 = af.tile(a,(1,2,3))
120+
print(a_tile2)
121+
# [manipulation7-endsnippet]
122+
123+
124+
# [manipulation8-snippet]
125+
126+
import arrayfire as af
127+
128+
a = af.randu((3,3))
129+
print(a) #[3 3 1 1]
130+
131+
''' 0.3949 0.8465 0.3709
132+
0.3561 0.9399 0.2751
133+
0.6097 0.6802 0.2720'''
134+
135+
136+
a_transpose = af.transpose(a)
137+
print(a_transpose) #[3 3 1 1]
138+
139+
''' 0.3949 0.3561 0.6097
140+
0.8465 0.9399 0.6802
141+
0.3709 0.2751 0.2720'''
142+
# [manipulation8-endsnippet]
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
Array and Matrix Manipulation
2+
=============================
3+
ArrayFire provides several different methods for manipulating arrays and matrices. The functionality includes:
4+
5+
* moddims() - change the dimensions of an array without changing the data
6+
* array() - create a (shallow) copy of an array with different dimensions.
7+
* flat() - flatten an array to one dimension
8+
* flip() - flip an array along a dimension
9+
* join() - join up to 4 arrays
10+
* reorder() - changes the dimension order within the array
11+
* shift() - shifts data along a dimension
12+
* tile() - repeats an array along a dimension
13+
* transpose() - performs a matrix transpose
14+
* T() - transpose a matrix or vector (shorthand notation)
15+
* H() - Hermitian Transpose (conjugate-transpose) a matrix
16+
17+
Below we provide several examples of these functions and their use.
18+
19+
flat()
20+
======
21+
The **flat()** function flattens an array to one dimension:
22+
23+
.. literalinclude:: arrayandmatrixmanipulation.py
24+
:language: python
25+
:start-after: [manipulation1-snippet]
26+
:end-before: [manipulation1-endsnippet]
27+
28+
The **flat** function can be called from Python as follows:
29+
30+
.. admonition:: Function
31+
32+
af.flat(array) - Python function for flattening an array
33+
34+
flip()
35+
======
36+
The **flip()** function flips the contents of an array along a chosen dimension. In the example below, we show the 5x2 array flipped along the zeroth (i.e. within a column) and first (e.g. across rows) axes:
37+
38+
39+
.. literalinclude:: arrayandmatrixmanipulation.py
40+
:language: python
41+
:start-after: [manipulation2-snippet]
42+
:end-before: [manipulation2-endsnippet]
43+
44+
The **flip** function can be called from Python as follows:
45+
46+
.. admonition:: Function
47+
48+
af.flip(array) - Python function for flipping an array
49+
50+
51+
join()
52+
======
53+
54+
The **join()** function joins arrays along a specific dimension. The C++ interface can join up to four arrays whereas the C interface supports up to 10 arrays. Here is an example of how to use join an array to itself:
55+
56+
.. literalinclude:: arrayandmatrixmanipulation.py
57+
:language: python
58+
:start-after: [manipulation3-snippet]
59+
:end-before: [manipulation3-endsnippet]
60+
61+
62+
The **join** function can be called from Python as follows:
63+
64+
.. admonition:: Function
65+
66+
af.join(0, array, array1) - Python function for joining arrays along a specified axis
67+
68+
moddims()
69+
=========
70+
71+
The **moddims()** function changes the dimensions of an array without changing its data or order. Note that this function modifies only the metadata associated with the array. It does not modify the content of the array. Here is an example of moddims() converting an 8x1 array into a 2x4 and then back to a 8x1:
72+
73+
.. literalinclude:: arrayandmatrixmanipulation.py
74+
:language: python
75+
:start-after: [manipulation4-snippet]
76+
:end-before: [manipulation4-endsnippet]
77+
78+
The moddims function has a single form in the Python API:
79+
80+
.. admonition:: Function
81+
82+
af.moddims(array, (3,2)) - Python function for modifying dimensions of an array
83+
84+
85+
reorder()
86+
=========
87+
The **reorder()** function modifies the order of data within an array by exchanging data according to the change in dimensionality. The linear ordering of data within the array is preserved.
88+
89+
.. literalinclude:: arrayandmatrixmanipulation.py
90+
:language: python
91+
:start-after: [manipulation5-snippet]
92+
:end-before: [manipulation5-endsnippet]
93+
94+
shift()
95+
=======
96+
The **shift()** function shifts data in a circular buffer fashion along a chosen dimension. Consider the following example:
97+
98+
99+
.. literalinclude:: arrayandmatrixmanipulation.py
100+
:language: python
101+
:start-after: [manipulation6-snippet]
102+
:end-before: [manipulation6-endsnippet]
103+
104+
The shift function can be called from Python as follows:
105+
.. admonition:: Function
106+
107+
af.shift(array, (3,2)) - Python function for shifting arrays along specified dimension
108+
109+
tile()
110+
======
111+
The **tile()** function repeats an array along the specified dimension. For example below we show how to tile an array along the zeroth and first dimensions of an array:
112+
113+
.. literalinclude:: arrayandmatrixmanipulation.py
114+
:language: python
115+
:start-after: [manipulation7-snippet]
116+
:end-before: [manipulation7-endsnippet]
117+
118+
.. admonition:: Function
119+
120+
af.tile(array, (3,2)) - Python function that tiles arrays along specified dimensions
121+
122+
123+
transpose()
124+
===========
125+
The **transpose()** function performs a standard matrix transpose. The input array must have the dimensions of a 2D-matrix.
126+
127+
.. literalinclude:: arrayandmatrixmanipulation.py
128+
:language: python
129+
:start-after: [manipulation8-snippet]
130+
:end-before: [manipulation8-endsnippet]
131+
132+
133+
The python interface for transpose is as follows:
134+
135+
.. admonition:: Function
136+
137+
af.transpose(array) - Python function to transpose matrix in place
138+
139+
140+
141+
array()
142+
=======
143+
**array()** can be used to create a (shallow) copy of a matrix with different dimensions. The total number of elements must remain the same. This function is a wrapper over the moddims() function discussed earlier.
144+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
ArrayFire JIT Code Generation
2+
=============================
3+
The ArrayFire library offers JIT (Just In Time) compiling for elementwise arithmetic operations. This includes trigonometric functions, comparisons, and element-wise operations.
4+
5+
At runtime, ArrayFire aggregates these function calls using an Abstract Syntax Tree (AST) data structure such that whenever a JIT-supported function is called, it is added into the AST for a given variable instance. The AST of the variable is computed if one of the following conditions is met:
6+
7+
* an explication evaluation is required by the programmer using the eval function, or
8+
* the variable is required to compute a different variable that is not JIT-supported.
9+
10+
When the above occurs, and the variable needs to be evaluated, the functions and variables in the AST data structure are used to create a single kernel. This is done by creating a customized kernel on-the-fly that is made up of all the functions in the AST. The customized function is then executed.
11+
12+
This JIT compilation technique has multiple benefits:
13+
14+
* A reduced number of kernel calls – a kernel call can be a significant overhead for small data sets.
15+
* Better cache performance – there are many instances in which the memory required by a single element in the array can be reused multiple times, or the temporary value of a computation can be stored in the cache and reused by future computations.
16+
* Temporary memory allocation and write-back can be reduced – when multiple expressions are evaluated and stored into temporary arrays, these arrays need to be allocated and the results written back to main memory.
17+
* Avoid computing elements that are not used – there are cases in which the AST is created for a variable; however, the expression is not used later in the computation. Thus, its evaluation can be avoided.
18+
* Better performance – all the above can help reduce the total execution time.
19+
20+
.. literalinclude:: afjit.py
21+
:language: python
22+
:start-after: [jit-snippet]
23+
:end-before: [jit-endsnippet]
24+
25+
26+
The above code computes the value of π using a Monte-Carlo simulation where points are randomly generated within the unit square. Each point is tested to see if it is within the unit circle. The ratio of points within the circle and square approximate the value π. The accuracy of π improves as the number of samples is increased, which motivates using additional samples.
27+
28+
There are two implementations above:
29+
30+
1. an implementation that does not benefit from the JIT (pi_no_jit), and
31+
2. an implementation that takes advantage of the JIT feature (pi_jit).
32+
33+
Specifically, as JIT is an integral feature of the ArrayFire library, it cannot simply be turned on and off. The only way for a programmer to sidestep the JIT operations is to manually force the evaluation of expressions. This is done in the non-JIT-supported implementation.
34+
35+
Timing these two implementations results in the following performance benchmark:
36+
37+
**Add Picture**
38+
39+
The above figure depicts the execution time (abscissa) as a function of the number of samples (ordinate) for the two implementations discussed above.
40+
41+
When the number of samples is small, the execution time of pi_no_jit is dominated by the launch of multiple kernels and the execution time pi_jit is dominated by on-the-fly compilation of the JIT code required to launch a single kernel. Even with this JIT compilation time, pi_jit outperforms pi_no_jit by 1.4-2.0X for smaller sample sizes.
42+
43+
When the number of samples is large, both the kernel launch overhead and the JIT code creation are no longer the limiting factors – the kernel’s computational load dominates the execution time. Here, the pi_jit outperforms pi_no_jit by 2.0-2.7X.
44+
45+
The number of applications that benefit from the JIT code generation is significant. The actual performance benefits are also application-dependent.

0 commit comments

Comments
 (0)