|
5 | 5 | "id": "8ad9f0d7",
|
6 | 6 | "metadata": {},
|
7 | 7 | "source": [
|
8 |
| - "# 0. Enoki cheat sheet\n", |
| 8 | + "# 0. Dr.Jit cheat sheet\n", |
9 | 9 | "\n",
|
10 | 10 | "## Overview\n",
|
11 | 11 | "\n",
|
12 |
| - "This short tutorial recaps the basic functionallities and routines of the Enoki library." |
| 12 | + "This short tutorial recaps the basic functionallities and routines of the Dr.Jit library." |
13 | 13 | ]
|
14 | 14 | },
|
15 | 15 | {
|
|
19 | 19 | "source": [
|
20 | 20 | "### Similarity with NumPy\n",
|
21 | 21 | "\n",
|
22 |
| - "On the Python side, the Enoki *syntax* is very similar to NumPy. Moreover, as we will see later, both frameworks are interoperable.\n", |
| 22 | + "On the Python side, the Dr.Jit *syntax* is very similar to NumPy. Moreover, as we will see later, both frameworks are interoperable.\n", |
23 | 23 | "\n",
|
24 |
| - "Let's first import both NumPy and Enoki using the alias `np` and `ek` respectively" |
| 24 | + "Let's first import both NumPy and Dr.Jit using the alias `np` and `dr` respectively" |
25 | 25 | ]
|
26 | 26 | },
|
27 | 27 | {
|
|
32 | 32 | "outputs": [],
|
33 | 33 | "source": [
|
34 | 34 | "import numpy as np\n",
|
35 |
| - "import enoki as ek" |
| 35 | + "import drjit as dr" |
36 | 36 | ]
|
37 | 37 | },
|
38 | 38 | {
|
39 | 39 | "cell_type": "markdown",
|
40 | 40 | "id": "a51da003",
|
41 | 41 | "metadata": {},
|
42 | 42 | "source": [
|
43 |
| - "Unlike NumPy, Enoki can perform array arithmetic on both CPU and GPU through various template variants which are exposed in top-level packages:\n", |
| 43 | + "Unlike NumPy, Dr.Jit can perform array arithmetic on both CPU and GPU through various template variants which are exposed in top-level packages:\n", |
44 | 44 | "\n",
|
45 | 45 | "| Variant | Description |\n",
|
46 | 46 | "| --------------- | ------------------------------------------------------------------ |\n",
|
47 |
| - "| `enoki.scalar` | Arrays built on top of scalars (float, int, etc.) |\n", |
48 |
| - "| `enoki.llvm` | Arrays built on top of LLVMArray |\n", |
49 |
| - "| `enoki.cuda` | Arrays built on top of CUDAArray |\n", |
50 |
| - "| `enoki.llvm.ad` | Similar to `enoki.llvm` but with automatic differentiation support |\n", |
51 |
| - "| `enoki.cuda.ad` | Similar to `enoki.cuda` but with automatic differentiation support |\n", |
| 47 | + "| `drjit.scalar` | Arrays built on top of scalars (float, int, etc.) |\n", |
| 48 | + "| `drjit.llvm` | Arrays built on top of LLVMArray |\n", |
| 49 | + "| `drjit.cuda` | Arrays built on top of CUDAArray |\n", |
| 50 | + "| `drjit.llvm.ad` | Similar to `drjit.llvm` but with automatic differentiation support |\n", |
| 51 | + "| `drjit.cuda.ad` | Similar to `drjit.cuda` but with automatic differentiation support |\n", |
52 | 52 | "\n",
|
53 | 53 | "These packages all contains various types like: `Bool, Float, Int, UInt, Array2f, Array2i, Matrix2f Matrix3f, ...`\n",
|
54 | 54 | "\n",
|
55 |
| - "Let's create some arrays using the `enoki.llvm` variants and play around with the NumPy interoperability:" |
| 55 | + "Let's create some arrays using the `drjit.llvm` variants and play around with the NumPy interoperability:" |
56 | 56 | ]
|
57 | 57 | },
|
58 | 58 | {
|
|
65 | 65 | "name": "stdout",
|
66 | 66 | "output_type": "stream",
|
67 | 67 | "text": [
|
68 |
| - "c -> (<class 'enoki.llvm.Float'>) = [9.0, 8.0, 7.0, 6.0]\n", |
| 68 | + "c -> (<class 'drjit.llvm.Float'>) = [9.0, 8.0, 7.0, 6.0]\n", |
69 | 69 | "d -> (<class 'numpy.ndarray'>) = [9. 8. 7. 6.]\n"
|
70 | 70 | ]
|
71 | 71 | }
|
72 | 72 | ],
|
73 | 73 | "source": [
|
74 |
| - "from enoki.llvm import Float, UInt32\n", |
| 74 | + "from drjit.llvm import Float, UInt32\n", |
75 | 75 | "\n",
|
76 | 76 | "# Create some floating-point arrays\n",
|
77 | 77 | "a = Float([1.0, 2.0, 3.0, 4.0])\n",
|
|
95 | 95 | "source": [
|
96 | 96 | "### Array construction routines\n",
|
97 | 97 | "\n",
|
98 |
| - "This section provides an overview of various Enoki routines (and their NumPy correspondence) to construct arrays." |
| 98 | + "This section provides an overview of various Dr.Jit routines (and their NumPy correspondence) to construct arrays." |
99 | 99 | ]
|
100 | 100 | },
|
101 | 101 | {
|
|
108 | 108 | "name": "stdout",
|
109 | 109 | "output_type": "stream",
|
110 | 110 | "text": [
|
111 |
| - "ek.zero: [0.0, 0.0, 0.0, 0.0, 0.0]\n", |
112 |
| - "ek.full: [0.10000000149011612, 0.10000000149011612, 0.10000000149011612, 0.10000000149011612, 0.10000000149011612]\n", |
113 |
| - "ek.arange: [0, 1, 2, 3, 4]\n", |
114 |
| - "ek.linespace: [0.0, 0.5, 1.0, 1.5, 2.0]\n" |
| 111 | + "dr.zero: [0.0, 0.0, 0.0, 0.0, 0.0]\n", |
| 112 | + "dr.full: [0.10000000149011612, 0.10000000149011612, 0.10000000149011612, 0.10000000149011612, 0.10000000149011612]\n", |
| 113 | + "dr.arange: [0, 1, 2, 3, 4]\n", |
| 114 | + "dr.linespace: [0.0, 0.5, 1.0, 1.5, 2.0]\n" |
115 | 115 | ]
|
116 | 116 | }
|
117 | 117 | ],
|
118 | 118 | "source": [
|
119 | 119 | "# Initialize floating-point array of size 5 with zeros\n",
|
120 |
| - "a = ek.zero(Float, 5) # np.zeros(5)\n", |
121 |
| - "print(f'ek.zero: {a}')\n", |
| 120 | + "a = dr.zero(Float, 5) # np.zeros(5)\n", |
| 121 | + "print(f'dr.zero: {a}')\n", |
122 | 122 | "\n",
|
123 | 123 | "# Initialize floating-point array of size 5 with a constant value\n",
|
124 |
| - "a = ek.full(Float, 0.1, 5) # np.ones(5, 0.4)\n", |
125 |
| - "print(f'ek.full: {a}')\n", |
| 124 | + "a = dr.full(Float, 0.1, 5) # np.ones(5, 0.4)\n", |
| 125 | + "print(f'dr.full: {a}')\n", |
126 | 126 | "\n",
|
127 |
| - "a = ek.arange(UInt32, 5) # np.arange(5)\n", |
128 |
| - "print(f'ek.arange: {a}')\n", |
| 127 | + "a = dr.arange(UInt32, 5) # np.arange(5)\n", |
| 128 | + "print(f'dr.arange: {a}')\n", |
129 | 129 | "\n",
|
130 | 130 | "# Return evenly spaced numbers over a specified interval\n",
|
131 |
| - "a = ek.linspace(Float, 0.0, 2.0, 5) # np.linspace(0.0, 2.0, 5)\n", |
132 |
| - "print(f'ek.linespace: {a}')" |
| 131 | + "a = dr.linspace(Float, 0.0, 2.0, 5) # np.linspace(0.0, 2.0, 5)\n", |
| 132 | + "print(f'dr.linespace: {a}')" |
133 | 133 | ]
|
134 | 134 | },
|
135 | 135 | {
|
|
139 | 139 | "source": [
|
140 | 140 | "### Masking\n",
|
141 | 141 | "\n",
|
142 |
| - "Writing codes using Enoki often means working with large arrays at once. Therefore it is not possible to use regular `if .. else ..` statements based on concret values, as different elements in the array might branch differently. This is where **masking** comes to the rescue! \n", |
| 142 | + "Writing codes using Dr.Jit often means working with large arrays at once. Therefore it is not possible to use regular `if .. else ..` statements based on concret values, as different elements in the array might branch differently. This is where **masking** comes to the rescue! \n", |
143 | 143 | "\n",
|
144 | 144 | "A mask (or `Bool`) is an array of boolean values that can be used to disable arithmetic operations on part of an array. It is possible to create such masks with any regular boolean arithmetic (e.g. `>, <, >=, <=`). \n",
|
145 | 145 | "\n",
|
146 |
| - "Often time, we combine masks with the `ek.select(mask, a, b)` statement which correspond to the ternary statement `mask ? a : b`. This is similar to the `np.where` function in NumPy." |
| 146 | + "Often time, we combine masks with the `dr.select(mask, a, b)` statement which correspond to the ternary statement `mask ? a : b`. This is similar to the `np.where` function in NumPy." |
147 | 147 | ]
|
148 | 148 | },
|
149 | 149 | {
|
|
156 | 156 | "name": "stdout",
|
157 | 157 | "output_type": "stream",
|
158 | 158 | "text": [
|
159 |
| - "x -> (<class 'enoki.llvm.Float'>) [0.0, 1.0, 2.0, 3.0, 4.0]\n", |
160 |
| - "m -> (<class 'enoki.llvm.Bool'>) [False, False, False, True, True]\n", |
161 |
| - "y -> (<class 'enoki.llvm.Float'>) [1.0, 1.0, 1.0, 4.0, 4.0]\n" |
| 159 | + "x -> (<class 'drjit.llvm.Float'>) [0.0, 1.0, 2.0, 3.0, 4.0]\n", |
| 160 | + "m -> (<class 'drjit.llvm.Bool'>) [False, False, False, True, True]\n", |
| 161 | + "y -> (<class 'drjit.llvm.Float'>) [1.0, 1.0, 1.0, 4.0, 4.0]\n" |
162 | 162 | ]
|
163 | 163 | }
|
164 | 164 | ],
|
165 | 165 | "source": [
|
166 |
| - "x = ek.arange(Float, 5)\n", |
| 166 | + "x = dr.arange(Float, 5)\n", |
167 | 167 | "m = x > 2.0 # True for all values of a that are greater than 2.0\n",
|
168 |
| - "y = ek.select(m, 4.0, 1.0) # Set the values greater than 2.0 to 4.0 otherwise to 1.0\n", |
| 168 | + "y = dr.select(m, 4.0, 1.0) # Set the values greater than 2.0 to 4.0 otherwise to 1.0\n", |
169 | 169 | "print(f'x -> ({type(x)}) {x}')\n",
|
170 | 170 | "print(f'm -> ({type(m)}) {m}')\n",
|
171 | 171 | "print(f'y -> ({type(y)}) {y}')"
|
|
178 | 178 | "source": [
|
179 | 179 | "### Basic math arithmetic\n",
|
180 | 180 | "\n",
|
181 |
| - "All common math operators like `+, -, /, *, *=, +=, %, //, ...` are supported with Enoki arrays.\n", |
| 181 | + "All common math operators like `+, -, /, *, *=, +=, %, //, ...` are supported with Dr.Jit arrays.\n", |
182 | 182 | "\n",
|
183 |
| - "Similarly to NumPy, Enoki provides all kinds of math arithmetic that can be performed on the entire array in a single call. Here is a non-exaustive list of those math functions:\n", |
| 183 | + "Similarly to NumPy, Dr.Jit provides all kinds of math arithmetic that can be performed on the entire array in a single call. Here is a non-exaustive list of those math functions:\n", |
184 | 184 | "`abs, min, max, sqrt, pow, sin, cos, tan, atan2, sincos, sec, cot, asin, acos, atan, exp, exp2, log, log2, sinh, cosh, tanh, asinh, acosh, atanh, ...`\n",
|
185 | 185 | "\n",
|
186 |
| - "Those routines are present in the root enoki package, hence can be used as follow:" |
| 186 | + "Those routines are present in the root drjit package, hence can be used as follow:" |
187 | 187 | ]
|
188 | 188 | },
|
189 | 189 | {
|
|
201 | 201 | }
|
202 | 202 | ],
|
203 | 203 | "source": [
|
204 |
| - "s, c = ek.sincos(a)\n", |
205 |
| - "m = ek.min(s, c)\n", |
| 204 | + "s, c = dr.sincos(a)\n", |
| 205 | + "m = dr.min(s, c)\n", |
206 | 206 | "print(f'm: {m}')"
|
207 | 207 | ]
|
208 | 208 | },
|
|
213 | 213 | "source": [
|
214 | 214 | "### Horizontal operations\n",
|
215 | 215 | "\n",
|
216 |
| - "Enoki also provides operations that require a pass over the entire array and return a single scalar value. Those operations are expensive as they will trigger a syncronization point, hence it is better to avoid them if possible.\n", |
| 216 | + "Dr.Jit also provides operations that require a pass over the entire array and return a single scalar value. Those operations are expensive as they will trigger a syncronization point, hence it is better to avoid them if possible.\n", |
217 | 217 | "\n",
|
218 | 218 | "The following snippet of code explores a few of those:"
|
219 | 219 | ]
|
|
229 | 229 | "output_type": "stream",
|
230 | 230 | "text": [
|
231 | 231 | "a: [1.0, 2.0, 3.0, 4.0, 5.0]\n",
|
232 |
| - "ek.hsum(a): 15.0\n", |
233 |
| - "ek.hprod(a): 120.0\n", |
234 |
| - "ek.hmean(a): 3.0\n", |
| 232 | + "dr.hsum(a): 15.0\n", |
| 233 | + "dr.hprod(a): 120.0\n", |
| 234 | + "dr.hmean(a): 3.0\n", |
235 | 235 | "m: [False, False, True, True, True]\n",
|
236 |
| - "ek.all(m): False\n", |
237 |
| - "ek.any(m): True\n", |
238 |
| - "ek.none(m): False\n" |
| 236 | + "dr.all(m): False\n", |
| 237 | + "dr.any(m): True\n", |
| 238 | + "dr.none(m): False\n" |
239 | 239 | ]
|
240 | 240 | }
|
241 | 241 | ],
|
242 | 242 | "source": [
|
243 |
| - "a = ek.arange(Float, 5) + 1\n", |
| 243 | + "a = dr.arange(Float, 5) + 1\n", |
244 | 244 | "print(f'a: {a}')\n",
|
245 | 245 | "\n",
|
246 | 246 | "# Horizontal sum\n",
|
247 |
| - "b = ek.hsum(a) # np.sum(a)\n", |
248 |
| - "print(f'ek.hsum(a): {b}')\n", |
| 247 | + "b = dr.hsum(a) # np.sum(a)\n", |
| 248 | + "print(f'dr.hsum(a): {b}')\n", |
249 | 249 | "\n",
|
250 | 250 | "# Horizontal product\n",
|
251 |
| - "b = ek.hprod(a) # np.prod(a)\n", |
252 |
| - "print(f'ek.hprod(a): {b}')\n", |
| 251 | + "b = dr.hprod(a) # np.prod(a)\n", |
| 252 | + "print(f'dr.hprod(a): {b}')\n", |
253 | 253 | "\n",
|
254 | 254 | "# Mean value over the entire array\n",
|
255 |
| - "b = ek.hmean(a) # np.mean(a)\n", |
256 |
| - "print(f'ek.hmean(a): {b}')\n", |
| 255 | + "b = dr.hmean(a) # np.mean(a)\n", |
| 256 | + "print(f'dr.hmean(a): {b}')\n", |
257 | 257 | "\n",
|
258 | 258 | "m = a > 2\n",
|
259 | 259 | "print(f'm: {m}')\n",
|
260 | 260 | "\n",
|
261 | 261 | "# True if all value of the mask array are True\n",
|
262 |
| - "b = ek.all(m) # np.all(m)\n", |
263 |
| - "print(f'ek.all(m): {b}')\n", |
| 262 | + "b = dr.all(m) # np.all(m)\n", |
| 263 | + "print(f'dr.all(m): {b}')\n", |
264 | 264 | "\n",
|
265 | 265 | "# True if any value of the mask array are True\n",
|
266 |
| - "b = ek.any(m) # np.any(m)\n", |
267 |
| - "print(f'ek.any(m): {b}')\n", |
| 266 | + "b = dr.any(m) # np.any(m)\n", |
| 267 | + "print(f'dr.any(m): {b}')\n", |
268 | 268 | "\n",
|
269 | 269 | "# True if no value of the mask array are True\n",
|
270 |
| - "b = ek.none(m) # ~np.any(m)\n", |
271 |
| - "print(f'ek.none(m): {b}')" |
| 270 | + "b = dr.none(m) # ~np.any(m)\n", |
| 271 | + "print(f'dr.none(m): {b}')" |
272 | 272 | ]
|
273 | 273 | },
|
274 | 274 | {
|
|
278 | 278 | "source": [
|
279 | 279 | "### `gather` and `scatter` routines\n",
|
280 | 280 | "\n",
|
281 |
| - "In programming languages like C++ or Python, it is possible to access the i-th element of an array using the `array[i]` syntax. This can both be used to read or write values in an array. Similarly, Enoki provides such read/write functionalities through the `ek.gather` and `ek.scatter` functions. Those are much more powerful than the regular array accessors as the index `i` can be an array itself! In which case the read operation (e.g. `ek.gather`) would return a array as well, not just a single value.\n", |
| 281 | + "In programming languages like C++ or Python, it is possible to access the i-th element of an array using the `array[i]` syntax. This can both be used to read or write values in an array. Similarly, Dr.Jit provides such read/write functionalities through the `dr.gather` and `dr.scatter` functions. Those are much more powerful than the regular array accessors as the index `i` can be an array itself! In which case the read operation (e.g. `dr.gather`) would return a array as well, not just a single value.\n", |
282 | 282 | "\n",
|
283 |
| - "Here is how one should use the `ek.gather` routine to read entries from an Enoki array:" |
| 283 | + "Here is how one should use the `dr.gather` routine to read entries from an Dr.Jit array:" |
284 | 284 | ]
|
285 | 285 | },
|
286 | 286 | {
|
|
300 | 300 | }
|
301 | 301 | ],
|
302 | 302 | "source": [
|
303 |
| - "source = ek.linspace(Float, 0, 1, 5)\n", |
| 303 | + "source = dr.linspace(Float, 0, 1, 5)\n", |
304 | 304 | "indices = UInt32([1, 2]) # Only read the 2nd and 3rd elements of the source array\n",
|
305 |
| - "result = ek.gather(Float, source, indices)\n", |
| 305 | + "result = dr.gather(Float, source, indices)\n", |
306 | 306 | "print(f'source: {source}')\n",
|
307 | 307 | "print(f'indices: {indices}')\n",
|
308 | 308 | "print(f'result: {result}')"
|
|
313 | 313 | "id": "07f6203b",
|
314 | 314 | "metadata": {},
|
315 | 315 | "source": [
|
316 |
| - "And here is how one can write entries at specific indices into a Enoki array" |
| 316 | + "And here is how one can write entries at specific indices into a Dr.Jit array" |
317 | 317 | ]
|
318 | 318 | },
|
319 | 319 | {
|
|
333 | 333 | }
|
334 | 334 | ],
|
335 | 335 | "source": [
|
336 |
| - "target = ek.zero(Float, 5)\n", |
| 336 | + "target = dr.zero(Float, 5)\n", |
337 | 337 | "indices = UInt32([0, 3, 4]) # Write to the first and last two elements of the target array\n",
|
338 | 338 | "source = Float([1.0, 2.0, 3.0])\n",
|
339 |
| - "ek.scatter(target, source, indices)\n", |
| 339 | + "dr.scatter(target, source, indices)\n", |
340 | 340 | "print(f'indices: {indices}')\n",
|
341 | 341 | "print(f'source: {source}')\n",
|
342 | 342 | "print(f'target: {target}')"
|
|
345 | 345 | ],
|
346 | 346 | "metadata": {
|
347 | 347 | "kernelspec": {
|
348 |
| - "display_name": "Python 3", |
| 348 | + "display_name": "Python 3 (ipykernel)", |
349 | 349 | "language": "python",
|
350 | 350 | "name": "python3"
|
351 | 351 | },
|
|
0 commit comments