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

Field

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

Taichi field 是全局数据容器,从 Python 作用域或 Taichi 作用域均能访问。 正如 NumPy 的 ndarray 或 PyTorch 的 tensor,Taichi 中的 field 被定义为多维元素数组,其中元素可以是标量、向量、矩阵或结构体。

note

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

标量 field

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

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

声明

声明标量 field 最简单的方法是调用 ti.field(dtype, shape),其中 dtype 是基础数据类型(参见 类型系统),shape 则是一个整数元组。

  • 声明一个零维标量 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 and f2 are basically interchangeable
    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 时,需要分别设置两个维度(行和列的数量)。 例如,下面的代码块定义了一个形状是 (3, 6) (即三行六列)的二维标量 field:

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

    f_2d 图示:

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

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

更高维的标量 field 可以用相似方式定义。

WARNING

Taichi 只支持维数 8 的 field。

访问标量 field 的元素

一旦 field 声明完成,Taichi 会自动将其元素初始化为 0。

要访问标量 field 的元素,请显式指定该元素的下标。

note

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

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

    f_0d[None] = 10.0

    f_0d 的布局:

        ┌──────┐
    10.0
    └──────┘
    └──────┘
    f_0d.shape=()
  • i 作为索引,访问一个一维 field 中的元素,i 是落在 [0, f_1d.shape[0]) 范围内的一个整数:

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

    f_1d 的布局:

    ┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
    012345678
    └───┴───┴───┴───┴───┴───┴───┴───┴───┘
  • 将整数对 (i, j) 作为索引,访问一个二维 field 中的元素。

    • i 落在 [0, f_2d.shape[0]) 范围内
    • j 落在 [0, f_2d.shape[1]) 范围内:
    for i, j in f_2d:
    f_2d[i, j] = i

    f_2d 的布局:

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

你可以用一个二维标量 field 来表示一个二维的数值网格。 下面的代码段创建并显示了一个 640 × 480 的灰度图,其中数值随机生成:

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 field 不支持切片。 以下用法均不正确:

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

以上用法都会导致系统抛出一条错误信息:Slicing is not supported on ti.field(ti.field 不支持切片)。

用给定值填充标量 field

调用field.fill(),将一个标量 field 中的元素全都设置为一个给定值:

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.

声明

声明一个元素都是 N 维向量的向量 field 与声明一个标量 field 类似,只是你需要调用 ti.Vector.field,而非 ti.field,并指定 N 作为第一个位置参数。

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

f 的布局:

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

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 的元素是矩阵。 在连续介质力学中,材料中的每一个微分单元点都存在着一个应变与应力张量。 应变与应力张量是一个 3×2 矩阵。 你可以使用矩阵 field 来代表这个张量场。

声明

以下代码片段声明了一个张量 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))

读取矩阵 field 的元素

访问矩阵 field 与访问向量 field 相似:使用索引运算符 [] 索引 field 元素,再用一个 [] 索引矩阵成员。

  • 获取矩阵 field tensor_fieldi, j 处元素:

    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)

注意事项:矩阵大小

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

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

在更大的矩阵上进行类似操作(如 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 存储用户自定义的结构体。 结构体元素的成员可以是:

  • 标量
  • 向量
  • 矩阵
  • 其他结构体 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
particle_field = ti.Struct.field({
"pos": ti.math.vec3,
"vel": ti.math.vec3,
"acc": ti.math.vec3,
"mass": float,
}, shape=(n,))

除了直接调用 ti.Struct.field() 以外,你也可以先声明一个复合类型 particle,然后为其创建一个 field:

# vec3 is a built-in vector type suppied in the `taichi.math` module
vec3 = ti.math.vec3
# 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,))

Access elements in a struct field

你可以使用以下两种方法的任一种来访问一个结构体 field 中某一元素的成员:下标优先法或名称优先法。

  • 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

由于 paticle_field.mass 是个包含了 paticle_field 中结构体的所有 mass 成员的 field,你也可以调用 fill() 来一次性设置所有成员为指定的值:

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