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 fieldf_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.0f_0d
的布局:┌──────┐
│ 10.0 │
└──────┘
└──────┘
f_0d.shape=()To access an element in a 1D field, use index
i
to get thei
-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
的布局:┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┘To access an element in a 2D field, use index
(i, j)
, which is an integer pair to get thei-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
的布局:┌───┬───┬───┬───┬───┬───┐
│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │
├───┼───┼───┼───┼───┼───┤
│ 1 │ 1 │ 1 │ 1 │ 1 │ 1 │
├───┼───┼───┼───┼───┼───┤
│ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │
└───┴───┴───┴───┴───┴───┘将整数 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
└ └──────┴──────┴──────┘ ┘
下面的代码片段声明了一个 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=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 fieldtensor_field
:mat = tensor_field[i, j]
获取元素
mat
的第一行、第二列成员:mat[0, 1]
ortensor_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.vector
、ti.types.matrix
和ti.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