diff --git a/docs/lang/articles/basic/external.md b/docs/lang/articles/basic/external.md index d22fa4d7adb01b..9aa58428d57dab 100644 --- a/docs/lang/articles/basic/external.md +++ b/docs/lang/articles/basic/external.md @@ -11,15 +11,15 @@ We use NumPy arrays as an example to illustrate the data transfer process becaus There are two ways to import a NumPy array `arr` to the Taichi scope: -- Create a Taichi field `f`, whose shape and dtype match the shape and dtype of `arr`, and call `f.from_numpy(arr)` to copy the data in `arr` into `f`. This approach is preferred when the original array is visited frequently from elsewhere in the Taichi scope (for example, in the case of texture sampling). +1. Create a Taichi field `f`, whose shape and dtype match the shape and dtype of `arr`, and call `f.from_numpy(arr)` to copy the data in `arr` into `f`. This approach is preferred when the original array is visited frequently from elsewhere in the Taichi scope (for example, in texture sampling). -- Pass `arr` as an argument to a kernel or a Taichi function using `ti.types.ndarray()` as type hint. The argument is passed by reference without creating a copy of `arr`. Thus, any modification to this argument from inside a kernel or Taichi function also changes the original array `arr`. This approach is preferred when the kernel or Taichi function that takes in the argument needs to process the original array (for storage or filtering, for example). +2. Pass `arr` as an argument to a kernel or a Taichi function using `ti.types.ndarray()` as type hint. The argument is passed by reference without creating a copy of `arr`. Thus, any modification to this argument from inside a kernel or Taichi function also changes the original array `arr`. This approach is preferred when the kernel or Taichi function that takes in the argument needs to process the original array (for storage or filtering, for example). :::note `from_numpy() / from_torch()` can take in any numpy array or torch Tensor, no matter it's contiguous or not. Taichi will manage its own copy of data. However, when passing an argument to a Taichi kernel, only contiguous numpy arrays or torch Tensors are supported. ::: -## Data transfer between NumPy arrays and Taichi fields +## Data Transfer between NumPy arrays and Taichi Fields To import data from a NumPy array to a Taichi field, first make sure that the field and the array have the same shape: @@ -46,7 +46,7 @@ arr = x.to_numpy() # [6, 7, 8]], dtype=int32) ``` -## Data transfer between PyTorch/Paddle tensors and Taichi fields +## Data Transfer between PyTorch/Paddle Tensors and Taichi Fields Data transfer between a PyTorch tensor and a Taichi field is similar to the NumPy case above: Call `from_torch()` for data import and `to_torch()` for data export. But note that `to_torch()` requires one more argument `device`, which specifies the PyTorch device: @@ -54,14 +54,14 @@ Data transfer between a PyTorch tensor and a Taichi field is similar to the NumP tensor = x.to_torch(device="cuda:0") print(tensor.device) # device(type='cuda', index=0) ``` - + For Paddle, you need to specify the device by calling `paddle.CPUPlace()` or `paddle.CUDAPlace(n)`, where `n` is an optional ID set to 0 by default. ## External array shapes -As mentioned before, when transferring data between a `ti.field/ti.Vector.field/ti.Matrix.field` and a NumPy array, you need to make sure that the shapes of both sides are in alignment. The shape matching rules are summarized as below: +When transferring data between a `ti.field/ti.Vector.field/ti.Matrix.field` and a NumPy array, you need to make sure that the shapes of both sides are aligned. The shape matching rules are summarized as below: -- When importing data to or exporting data from a scalar field, ensure that **the shape of the corresponding NumPy array, PyTorch tensor, or Paddle tensor equals the shape of the scalar field** +1. When importing data to or exporting data from a scalar field, ensure that **the shape of the corresponding NumPy array, PyTorch tensor, or Paddle tensor equals the shape of the scalar field** ```python field = ti.field(int, shape=(256, 512)) @@ -89,7 +89,7 @@ As mentioned before, when transferring data between a `ti.field/ti.Vector.field/ └ └───┴───┴───┴───┴───┴───┘ ┘ ``` -- When importing data to or exporting data from an `n`-dimensional vector field, ensure that **the shape of the corresponding NumPy array, PyTorch tensor, or Paddle tensor is set to** `(*field_shape, n)`: +2. When importing data to or exporting data from an `n`-dimensional vector field, ensure that **the shape of the corresponding NumPy array, PyTorch tensor, or Paddle tensor is set to** `(*field_shape, n)`: ```python field = ti.Vector.field(3, int, shape=(256, 512)) @@ -118,7 +118,7 @@ As mentioned before, when transferring data between a `ti.field/ti.Vector.field/ └ └─────────┴─────────┴─────────┘ ┘ ``` -- When importing data to or exporting data from an `n`-by-`m` (`n x m`) matrix field, ensure that **the shape of the corresponding NumPy array, PyTorch tensor, or Paddle tensor is set to** `(*field_shape, n, m)`: +3. When importing data to or exporting data from an `n`-by-`m` (`n x m`) matrix field, ensure that **the shape of the corresponding NumPy array, PyTorch tensor, or Paddle tensor is set to** `(*field_shape, n, m)`: ```python field = ti.Matrix.field(3, 4, ti.i32, shape=(256, 512)) @@ -132,7 +132,7 @@ As mentioned before, when transferring data between a `ti.field/ti.Vector.field/ field.from_numpy(array) # the input array must be of shape (256, 512, 3, 4) ``` -- When importing data to a struct field, export the data of the corresponding external array as **a dictionary of NumPy arrays, PyTorch tensors, or Paddle tensors** with keys being struct member names and values being struct member arrays. Nested structs are exported as nested dictionaries: +4. When importing data to a struct field, export the data of the corresponding external array as **a dictionary of NumPy arrays, PyTorch tensors, or Paddle tensors** with keys being struct member names and values being struct member arrays. Nested structs are exported as nested dictionaries: ```python field = ti.Struct.field({'a': ti.i32, 'b': ti.types.vector(3, float)}, shape=(256, 512)) @@ -146,7 +146,7 @@ As mentioned before, when transferring data between a `ti.field/ti.Vector.field/ field.from_numpy(array_dict) # the input array must have the same keys as the field ``` -## Using external arrays as Taichi kernel arguments +## Using External Arrays as Taichi Kernel Arguments Use type hint `ti.types.ndarray()` to pass external arrays as kernel arguments. For example: @@ -169,13 +169,15 @@ print(a) This is an entry-level example to show you how to call `ti.types.ndarray()`. We now illustrate a more advanced usage of this method. -Assume that `a` and `b` are both 2D arrays of the same shape and dtype. For each cell `(i, j)` in `a`, we want to calculate the difference between its value and the average of its four neighboring cells while storing the result in the corresponding cell in `b`. In this case, cells on the boundary, which are cells with fewer than four neighbors, are ruled out for simplicity. This operation is usually denoted as the *discrete Laplace operator*: +### Advanced + +Assume that `a` and `b` are both 2D arrays of the same shape and dtype. For each cell `(i, j)` in `a`, we want to calculate the difference between its value and the average of its four neighboring cells while storing the result in the corresponding cell in `b`. In this case, cells on the boundary, which are cells with fewer than four neighbors, are ruled out for simplicity. This operation is usually denoted as the *Discrete Laplace Operator*: ``` b[i, j] = a[i, j] - (a[i-1, j] + a[i, j-1] + a[i+1, j] + a[i, j+1]) / 4 ``` -Such an operation is usually very slow even with NumPy's vectorization, as the following code snippet shows: +Such an operation is typically very slow, even with NumPy's vectorization as shown below: ```python b[1:-1, 1:-1] += ( a[ :-2, 1:-1] + @@ -194,9 +196,11 @@ def test(a: ti.types.ndarray(), b: ti.types.ndarray()): # assume a, b have the b[i, j] = a[i, j] - (a[i-1, j] + a[i, j-1] + a[i+1, j] + a[i, j+1]) / 4 ``` -This code snippet is more readable than the NumPy version above and runs way faster even on the CPU backend. +Not only is this code snippet is more readable than the NumPy version above, but it also runs way faster even on the CPU backend. -Note that the elements in an external array must be indexed using a single square bracket. This contrasts with a Taichi vector field or matrix field where field members and elements are indexed separately: +:::note +The elements in an external array must be indexed using a single square bracket. This contrasts with a Taichi vector field or matrix field where field members and elements are indexed separately: +::: ```python x = ti.Vector.field(3, float, shape=(5, 5))