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

Field

field 是从数学和物理学中借用的术语。 如果你已经了解 标量场(例如热量场)或向量场(例如 引力场),那么理解 Taichi 中的 field 就很容易。

Taichi field 是全局数据容器,从 Python 作用域或 Taichi 作用域均能访问。 Just like an ndarray in NumPy or a tensor in PyTorch, a field in Taichi is defined as a multi-dimensional array of elements, and elements in a field can be a Scalar, a Vector, a Matrix, or a Struct.

标量 field

Scalar field 存储的是标量,是最基本的 field。

  • 一个0D 的标量 field 是单个标量。
  • 一个一维标量 field 是由标量组成的一个一维数组。
  • 一个二维标量 field 是由标量组成的一个二维数组,以此类推。

声明

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 the Type System and shape is a tuple of integers.

  • 声明一个零维标量 field 时,将形状设置成空元组 ()

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

    f_0d 图示:

        ┌─────┐
    │ │
    └─────┘
    └─────┘
    f_0d.shape=()
  • 声明一个长度为 n 的一维标量 field 时,将形状设置成 n(n,)

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

    f_1d 图示:

    ┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
    │ │ │ │ │ │ │ │ │ │
    └───┴───┴───┴───┴───┴───┴───┴───┴───┘
    └───────────────────────────────────┘
    f_1d.shape = (9,)

    零维 field 和长度为 1 的一维 field 之间除了索引规则外并无区别。 你必须使用 None 作为索引访问一个零维 field,用 0 作为索引访问一个长度为 1 的一维 field:

    f1 = ti.field(int, shape=())
    f2 = ti.field(int, shape=1)

    f1[None] = 1 # Use None to access a 0D field
    f2[0] = 1 # Use 0 to access a 1D field of length 1
  • 声明一个二维标量 field 时,需要分别设置两个维度(行和列的数量)。 For example, the following code snippet defines a 2D scalar field with the shape (3, 6) (three rows and six columns):

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

    Here is an illustration of f_2d:

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

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

Scalar fields of higher dimensions can be similarly defined.

WARNING

Taichi only supports fields of dimensions 8.

Accessing elements in a scalar field

Once a field is declared, Taichi automatically initializes its elements to zero.

To access an element in a scalar field, you need to explicitly specify the element's index.

note

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

  • None 作为索引,访问一个零维 field 中的元素,尽管该 field 中仅有一个元素。

    f_0d = ti.field(ti.f32, shape=())
    f_0d[None] = 10.0

    f_0d 的布局:

        ┌──────┐
    10.0
    └──────┘
    └──────┘
    f_0d.shape=()
  • To access an element in a 1D field, use index i to get the i-th element of our defined field.

    f_1d = ti.field(ti.f32, shape=(9,))

    @ti.kernel
    def loop_over_1d():
    for i in range(9):
    f_1d[i] = i

    loop_over_1d()

    f_1d 的布局:

    ┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
    012345678
    └───┴───┴───┴───┴───┴───┴───┴───┴───┘
  • To access an element in a 2D field, use index (i, j), which is an integer pair to get the i-th, j-th element of our defined field.

    f_2d = ti.field(ti.f32, shape=(16, 16))

    @ti.kernel
    def loop_over_2d():
    for i, j in f_2d:
    f_2d[i, j] = i

    loop_over_2d()

    f_2d 的布局:

    ┌───┬───┬───┬───┬───┬───┐
    000000
    ├───┼───┼───┼───┼───┼───┤
    111111
    ├───┼───┼───┼───┼───┼───┤
    222222
    └───┴───┴───┴───┴───┴───┘
  • 将整数 n 元组 (i, j, k, ...) 作为索引,访问一个 n 维 field 中的元素。

You can use a 2D scalar field to represent a 2D grid of values. The following code snippet creates and displays a 640×480 gray scale image of randomly-generated values:

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

width, height = 640,480
# Creates 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():
# Fills the image with random gray
for i,j in gray_scale_image:
gray_scale_image[i,j] = ti.random()

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

Taichi fields do not support slicing. Neither of the following usages are correct:

for x in f_2d[0]:  # Error! You tried to access its first row,but it is not supported
...
f_2d[0][3:] = [4, 5, 6]  # Error! You tried to access a slice of the first row, but it is not supported

Either way, the system throws an error message 'Slicing is not supported on ti.field'.

用给定值填充标量 field

To set all elements in a scalar field to a given value, call field.fill():

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

@ti.kernel
def test():
x.fill(-1) # Sets 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 的元素是向量。 向量的含义依赖于程序所处的场景。 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 declaring a scalar field, except that you need to call 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:

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

The layout of f:

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

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

The following code snippet declares a 300x300x300 vector field volumetric_field, whose vector dimension is 4:

box_size = (300, 300, 300)  # A 300x300x300 grid in a 3D space

# Declares a 300x300x300 vector field, whose vector dimension is n=4
volumetric_field = ti.Vector.field(n=4, dtype=ti.f32, shape=box_size)

Accessing elements in a vector field

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

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

    volumetric_field[i, j, k]

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

    volumetric_field[i, j, k][l]

note

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 # Assigns 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

如名称所示,矩阵 field 的元素是矩阵。 In continuum mechanics, at each infinitesimal point in a 3D material exists a strain and stress tensor, which is a 3 x 2 matrix. We can use a matrix field to represent this tensor.

声明

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

# Declares 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))

Accessing elements in a matrix field

Accessing a matrix field is similar to accessing a vector field: You use an index operator [] for field indexing and a second index operator [] for matrix indexing.

  • To access the i-th, j-th element of the matrix field tensor_field:

    mat = tensor_field[i, j]

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

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

note

To access the 0D matrix field x = ti.Matrix.field(n=3, m=4, dtype=ti.f32, shape=()):

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

注意事项:矩阵大小

Matrix operations are unrolled at compile time. 请看以下代码片段:

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 at 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

Operating on larger matrices (for example 32x128) can lead to longer compilation time and poor performance. 出于性能考虑,建议您将矩阵保持在最小水平:

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

临时解决方法:

When declaring a matrix field, leave large dimensions to the fields, rather than to the matrices. 如果您有一个由 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 存储用户自定义的结构体。 结构体元素的成员可以是:

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

声明

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

  • 成员变量 pos, vel, acc, 和 质量 均以字典格式提供。
  • 你可以将 复合类型,比如 ti.types.vectorti.types.matrixti.types.struct,作为结构体成员,声明向量、矩阵或结构体。
# Declares a 1D struct field using the ti.Struct.field() method
n = 10
particle_field = ti.Struct.field({
"pos": ti.math.vec3,
"vel": ti.math.vec3,
"acc": ti.math.vec3,
"mass": float,
}, shape=(n,))

Besides 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
n = 10
# Declares a struct comprising three vectors and one floating-point number
particle = ti.types.struct(
pos=vec3, vel=vec3, acc=vec3, mass=float,
)
# Declares a 1D field of the struct particle by calling field()
particle_field = particle.field(shape=(n,))

Accessing 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:
# Sets the position of the first particle in the field to [0.0, 0.0, 0.0]
particle_field[0].pos = vec3(0) # particle_field is a 1D struct field, pos is a 3D vector
  • 名称优先法则先创建一个子 field,集合该结构体 field 中所有 mass 成员,再用索引操作符 [] 访问特定成员:
particle_field.mass[0] = 1.0  # Sets the mass of the first particle in the field to 1.0

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

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