跳转至主要内容
Version: v1.1.3

Field

field是从数学和物理学中借用的术语。 如果您已经知道数学和物理学中的 标量场 (例如温度场)、向量场 (例如 引力场) ,那么理解 Taichi 中的 field 就会很容易。

Taichi field 是能够从Python scope 和Taichi scope 访问的 全局的 数据容器。 就像NumPy 中的 ndarray 或 PyTorch 中的 tensor一样,Taichi 中的 field 被定义为多维元素数组。 一个 field 中的元素可以是标量、向量、矩阵或结构体。

note

A 0D (zero-dimensional) field contains only one element.

标量 field

Scalar field 存储的是标量,是最基本的 field。 A 0D scalar field is a single scalar; a 1D scalar field is a 1D array of scalars; a 2D scalar field is a 2D array of scalars, and so on and so forth.

声明

The simplest way to declare a scalar field is to call ti.field(dtype, shape), where dtype is a primitive data type as explained in type system and shape is a tuple of integers.

  • To declare a 0D scalar field, set its shape to the empty tuple ():

    # Declare a 0D scalar field whose data type is f32
    f_0d = ti.field(ti.f32, shape=()) # 0D field

    An illustration of f_0d is shown below:

        ┌─────┐
    │ │
    └─────┴
    └─────┘
    f_0d.shape=()
  • To declare a 1D scalar field of length n, set its shape to n or (n,):

    f_1d = ti.field(ti.i32, shape=9)  # 1D field

    An illustration of f_1d is shown below:

    ┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
    │ │ │ │ │ │ │ │ │ │
    └───┴───┴───┴───┴───┴───┴───┴───┴───┘
    └───────────────────────────────────┘
    f_1d.shape = (9,)
  • To declare a 2D scalar field, set the sizes of its first two dimensions, i.e., the number of rows and columns, respectively. For example, the following code defines a 2D scalar field of shape (3, 6), which has 3 rows and 6 columns:

    f_2d = ti.field(int, shape=(3, 6))  # 2D field

    An illustration of f_2d is shown below:

                           f_2d.shape[1]
    (=6)
    ┌───────────────────────┐

    ┌ ┌───┬───┬───┬───┬───┬───┐ ┐
    │ │ │ │ │ │ │ │ │
    │ ├───┼───┼───┼───┼───┼───┤ │
    f_2d.shape[0] │ │ │ │ │ │ │ │ │
    (=3) │ ├───┼───┼───┼───┼───┼───┤ │
    │ │ │ │ │ │ │ │ │
    └ └───┴───┴───┴───┴───┴───┘ ┘

Scalar fields of higher dimensions can be similarily defined.

warning

Taichi only supports fields of dimensions <= 8.

读取标量 field 的元素

Once a field is declared, Taichi automatically assigns an initial value of zero to its elements.

To access an element in a scalar field, you need to explicitly use the index of the element.

note

When accessing a 0D field x, use x[None] = 0, not x = 0.

  • To access the element in a 0D field, you are required to use the index None even though it has only one element:

    f_0d[None] = 10.0

    The value in f_0d will be like:

        ┌──────┐
    10.0
    └──────┘
    └──────┘
    f_0d.shape=()
  • To access an element in a 1D field, you are required to use its index i, with i being an integer in the range [0, f_1d.shape[0]):

    for i in range(9):
    f_1d[i] = i

    The elements in f_1d will be like:

    ┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
    012345678
    └───┴───┴───┴───┴───┴───┴───┴───┴───┘
  • To access an element in a 2D field, you are required to use its index (i, j), which is a pair of integers with i in the range [0, f_2d.shape[0] - 1) and j in the range [0, f_2d.shape[1] - 1):

    for i, j in f_2d:
    f_2d[i, j] = i

    The elements in f_2d will be like:

    ┌───┬───┬───┬───┬───┬───┐
    000000
    ├───┼───┼───┼───┼───┼───┤
    111111
    ├───┼───┼───┼───┼───┼───┤
    222222
    └───┴───┴───┴───┴───┴───┘
  • Similarily, an element in an n-dimensional field is indexed by a n-tuple of integers, and you will need n integers (i, j, k, ...) to access it.

As illustrated above, you can use a 2D scalar field to represent a 2D grid of values. 下面的代码片段创建并显示了一个 640×480 的图像,并随机生成灰度:

import taichi as ti
ti.init(arch=ti.cpu)

width, height = 640,480
# Create a 640x480 scalar field, each of its elements representing a pixel value (f32)
gray_scale_image = ti.field(dtype=ti.f32, shape=(width, height))

@ti.kernel
def fill_image():
# Fill the image with random gray
for i,j in gray_scale_image:
gray_scale_image[i,j] = ti.random()

fill_image()
# Create a GUI of same size as the gray-scale image
gui = ti.GUI('gray-scale image with random values', (width, height))
while gui.running:
gui.set_image(gray_scale_image)
gui.show()
WARNING

Taichi does not support slicing on a Taichi field. You should always use n integers as indices to access an element, and n equals the number of dimensions of the field. For example, with the 2D scalar field f_2d above, you may try to use f_2d[0] to access its first row:

for x in f_2d[0]:  # Error!
...

Or you may want to access a slice of the first row:

f_2d[0][3:] = [4, 5, 6]  # Error!

Either way, you will see the error raised: "Slicing is not supported on ti.field".

Fill a scalar field with a given value

You can call the field.fill() method to set all elements in a scalar field to a given value. For example:

x = ti.field(int, shape=(5, 5))
x.fill(1) # set all elements in x to 1

@ti.kernel
def test():
x.fill(-1) # set all elements in x to -1

元数据

元数据提供了标量 field 的基本信息。 You can retrieve the data type and shape of a scalar field via its shape and dtype properties:

f_1d.shape  # (9,)
f_3d.dtype # f32

向量 field

如名称所示,向量 field 的元素是向量。 What a vector represents depends on the scenario of your program. For example, a vector may stand for the (R, G, B) triple of a pixel, the position of a particle, or the gravitational field in space.

声明

Declaring a vector field where each element is an N-dimensional vector is similar to the way you declare a scalar field, except that you need to call the function ti.Vector.field instead of ti.field and specify N as the first positional argument.

For example, the following code snippet declares a 2D field of 2D vectors:

# Declare a 3x3 vector field comprising 2D vectors
f = ti.Vector.field(n=2, dtype=float, shape=(3, 3))

The memory layout of f will be like:

                     f.shape[1]
(=3)
┌────────────────────┐

┌ ┌──────┬──────┬──────┐ ┐
│ │[*, *][*, *][*, *]│ │
│ ├──────┼──────┼──────┤ │
f.shape[0] │ │[*, *][*, *][*, *]│ │ [*, *]
(=3) │ ├──────┼──────┼──────┤ │ └─────┘
│ │[*, *][*, *][*, *]│ │ n=2
└ └──────┴──────┴──────┘ ┘

下面的代码片段声明了一个 300x300x300 向量 field volumetric_field, 其向量的尺寸是 3:

box_size = (300, 300, 300)  # A 300x300x300 grid in a 3D space
# Declare a 300x300x300 vector field, whose vector dimension is n=3
volumetric_field = ti.Vector.field(n=3, dtype=ti.f32, shape=box_size)

读取向量 field 的元素

读取向量 field 类似于访问多维数组:您使用索引运算符[] 来访问 field 中的一个元素。 唯一的区别是,要访问某个元素的特定组件(在这里指向量), 您需要一个 额外的 索引运算符 [] :

  • 读取上述体积场特定位置的速度向量:

    volumetric_field[i, j, k]

  • 读取速度向量的第 l 个组件:

    volumetric_field[i, j, k][l]

  • Alternatively, you can use swizzling with the indices xyzw or rgba to access the components of a vector, provided that the dimension of the vector is no more than four:

    volumetric_field[i, j, k].x = 1  # equivalent to volumetric_field[i, j, k][0] = 1
    volumetric_field[i, j, k].y = 2 # equivalent to volumetric_field[i, j, k][1] = 2
    volumetric_field[i, j, k].z = 3 # equivalent to volumetric_field[i, j, k][2] = 3
    volumetric_field[i, j, k].w = 4 # equivalent to volumetric_field[i, j, k][3] = 4
    volumetric_field[i, j, k].xyz = 1, 2, 3 # assign 1, 2, 3 to the first three components
    volumetric_field[i, j, k].rgb = 1, 2, 3 # equivalent to the above

以下代码片段生成并打印一个随机向量 field:

# n: vector dimension; w: width; h: height
n, w, h = 3, 128, 64
vec_field = ti.Vector.field(n, dtype=float, shape=(w,h))

@ti.kernel
def fill_vector():
for i,j in vec_field:
for k in ti.static(range(n)):
#ti.static unrolls the inner loops
vec_field[i,j][k] = ti.random()

fill_vector()
print(vec_field[w-1,h-1][n-1])
note

To access the p-th component of the 0D vector field x = ti.Vector.field(n=3, dtype=ti.f32, shape=()):

x[None][p] (0 p < n).

矩阵场

如名称所示,矩阵 field 的元素是矩阵。 在连续介质力学中,材料中的每一个微分单元点都存在着一个应变与应力张量。 应变与应力张量是一个 3×2 矩阵。 你可以使用矩阵 field 来代表这个张量场。

声明

以下代码片段声明了一个张量 field:

# Declare a 300x400x500 matrix field, each of its elements being a 3x2 matrix
tensor_field = ti.Matrix.field(n=3, m=2, dtype=ti.f32, shape=(300, 400, 500))

读取矩阵 field 的元素

读取矩阵 field 与读取向量 field 相似:使用索引运算符 [] 进行 field 索引,第二个 [] 进行矩阵索引。

  • 获取矩阵 field 的 i, j 元素 tensor_field:

    mat = tensor_field[i, j]

  • 获取元素 mat 第一行和第二列上的成员:

    mat[0, 1] or tensor_field[i, j][0, 1]

note

要访问 0D 矩阵 field x = ti.Matrix.field(n=3, m=4, dtype=ti.f32, shape=()):

x[None][p, q] (0 p < n, 0 q < m)

注意事项:矩阵大小

矩阵操作在编译期间被展开。 请看以下代码片段:

import taichi as ti
ti.init()

a = ti.Matrix.field(n=2, m=3, dtype=ti.f32, shape=(2, 2))
@ti.kernel
def test():
for i in ti.grouped(a):
# a[i] is a 2x3 matrix
a[i] = [[1, 1, 1], [1, 1, 1]]
# The assignment is unrolled to the following during compile time:
# a[i][0, 0] = 1
# a[i][0, 1] = 1
# a[i][0, 2] = 1
# a[i][1, 0] = 1
# a[i][1, 1] = 1
# a[i][1, 2] = 1

在大型矩阵上进行类似操作(例如, 32x128) 可能导致很长的编译时间和性能变差。 出于性能考虑,建议您将矩阵保持在最小水平:

  • 2x1, 3x3, 和 4x4 矩阵可以正常工作。
  • 32x6 有点过大。

临时解决方法:

在声明矩阵 field 时,将大尺寸留给 field 而不是留给矩阵。 如果您有一个由 64x32 矩阵组成的 3x2 field

  • 不推荐: ti.Matrix.field(64, 32, dtype=ti.f32, shape=(3, 2))
  • 推荐: ti.Matrix.field(3, 2, dtype=ti.f32, shape=(64, 32))

结构场

结构体 field 存储用户自定义的结构体。 结构体元素的成员可以是:

  • 标量
  • 向量
  • 矩阵
  • 其他结构体 field。

声明

下面的代码片段使用 ti.Struct.field() 来声明粒子信息的 1D 字段(位置、速度、加速度和质量)。 请注意:

  • 成员变量 pos, vel, acc, 和 质量 均以字典格式提供。
  • Compound types, such as ti.types.vector, ti.types.matrix, and ti.types.struct, can be used to declare vectors, matrices, or structs as struct members.
# Declare a 1D struct field using the ti.Struct.field() method
particle_field = ti.Struct.field({
"pos": ti.math.vec3,
"vel": ti.math.vec3,
"acc": ti.math.vec3),
"mass": float,
}, shape=(n,))

Alternatively, instead of directly using ti.Struct.field(), you can first declare a compound type particle and then create a field of it:

# vec3 is a built-in vector type suppied in the `taichi.math` module
vec3 = ti.math.vec3
# Declare a struct composed of three vectors and one floating-point number
particle = ti.types.struct(
pos=vec3, vel=vec3, acc=vec3, mass=float,
)
# Declare a 1D field of the struct particle using field()
particle_field = particle.field(shape=(n,))

Access elements in a struct field

You can access a member of an element in a struct field in either of the following ways: "index-first" or "name-first".

  • The index-first approach locates a certain element with its index before specifying the name of the target member:
# Set the position of the first particle in the field to origin [0.0, 0.0, 0.0]
particle_field[0].pos = vec3(0) # pos is a 3D vector

The name-first approach, in contrast, first creates the sub-field that gathers all the mass members in the struct field and then uses the index to access a specific one:

particle_field.mass[0] = 1.0  # Set the mass of the first particle in the field to 1.0

Considering that paticle_field.mass is a field consisting of all the mass members of the structs in paticle_field, we can also call its fill() method to set the members to a specific value all at once:

particle_field.mass.fill(1.0)  # Set all mass of the particles in the struct field to 1.0