跳转至

Type System

1062 个字 86 行代码 预计阅读时间 5 分钟

Taichi 是一门静态类型的编程语言,Taichi 中的变量类型是在编译时就确定的,一旦声明了变量,就不能再为其分配不同类型的值

Example

@ti.kernel
def test():
    x = 1  # x is the integer 1
    x = 3.14  # x is an integer, so the value 3.14 is cast to 3 and x takes the value 3
    x = ti.Vector([1, 1])  # Error!

上述代码的最后一行会报错,因为ti.Vector()类型的值不能赋值给变量x

Taichi 中的ti.types模块定义了所有 Taichi 支持的数据类型,这些数据类型分为两类 : 基本类型 (primitive) 和复合类型 (compound)

  • 基本类型包括常用的数值数据类型,比如ti.i32(int32) , ti.u8(uint8) , ti.f64(float64)
  • 复合类型包括类似数组或类似结构的数据类型,这些类型由多个成员组成,这些成员可以是基本类型,也可以是其他复合类型,比如ti.types.matrix , ti.types.ndarray , ti.types.struct

基本类型

i表示有符号整数,u表示无符号整数,f表示浮点数,后面跟的位数可以是 8,16,32,64,最常用的两种基本类型为

  • i32: 32 位有符号整数,也是默认的整数类型
  • f32: 32 位浮点数,也是默认的浮点数类型

需要注意,不同 backend Taichi 基本类型的支持会有所不同,详见下表:

不同 backend 对基本类型的支持对比

Taichi 允许在调用ti.init()时指定默认的基本数据类型,用法如下:

ti.init(default_ip=ti.i64)  # Sets the default integer type to ti.i64
ti.init(default_fp=ti.f64)  # Sets the default floating-point type to ti.f64

在应用中如果要保持数据的高精度,建议将default_fp设置为ti.F64

数据类型别名

Taichi scope 中,intfloat会被分别用作默认整数和浮点类型的别名

ti.init(default_ip=ti.i64, default_fp=ti.f64)

@ti.kernel
def example_cast() -> int:  # the returned type is ti.i64
    x = 3.14    # x is of ti.f64 type
    y = int(x)  # equivalent to ti.i64(x)
    return y

Python scope 中,创建一个 Taichi data container 并指定类型时,使用的也是别名

x = ti.field(float, 5)
# Is equivalent to:
x = ti.field(ti.f64, 5)

除了 Taichi scope Python scope 创建的 Taichi data container 以外,intfloat作为 Python 中的内置函数

显式类型转换

可以用ti.cast()函将给定值转换为特定的目标类型

@ti.kernel
def foo():
    a = 3.14
    b = ti.cast(a, ti.i32)  # 3
    c = ti.cast(b, ti.f32)  # 3.0

还有一种更方便的方式,使用基本类型直接转换

@ti.kernel
def foo():
    a = 3.14
    x = int(a)    # 3
    y = float(a)  # 3.14
    z = ti.i32(a)  # 3
    w = ti.f64(a)  # 3.14

隐式类型转换

Taichi 里,隐式类型转换发生在二元操作和赋值操作中

Warning

隐式类型转换通常是 bug 产生的来源,因此不推荐使用隐式类型转换,而是显式地指定变量类型和传入的数据

隐式类型转换的规则如下:

隐式类型转换的规则

有一些例外:

  • 逻辑运算的返回值类型为i32
  • 比较运算的返回值类型为i32

赋值操作中发生的隐式类型转换

Example1

@ti.kernel
def foo():
    a = 3.14
    a = 1
    print(a)  # 1.0

将变量a的值从int 1转成float 1.0

Example2

@ti.kernel
def foo():
    a = 1
    a = 3.14
    print(a)  # 3

将变量a的值从float 3.14转成int 3

由此可见,初始化时的变量类型决定了隐式类型转换的结果

复合类型

矩阵和向量

可以用ti.types.matrix()ti.types.vector()来自定义创建矩阵和向量数据类型

Example

vec4d = ti.types.vector(4, ti.f64)  # a 64-bit floating-point 4D vector type
mat4x3i = ti.types.matrix(4, 3, int)  # a 4x3 integer matrix type

在上述代码中,我们分别创建了两个类型

  • vec4d: 元素为 64 位浮点数的 4 维向量类型
  • mat4x3i: 元素为整数的 4x3 矩阵类型

可以利用自定义的复合类型来实例化向量和矩阵,以及作为函数参数的数据类型

Example

v = vec4d(1, 2, 3, 4)  # Create a vector instance, here v = [1.0 2.0 3.0 4.0]

@ti.func
def length(w: vec4d):  # vec4d as type hint
    return w.norm()

@ti.kernel
def test():
    print(length(v))

结构体和数据类 (dataclass)

可以用ti.types.struct()函数来创建一个结构体类型,以下是一个创建球体类型 ( sphere_type ) 的例子

Example

# Define a compound type vec3 to represent a sphere's center
vec3 = ti.types.vector(3, float)
# Define a compound type sphere_type to represent a sphere
sphere_type = ti.types.struct(center=vec3, radius=float)
# Initialize sphere1, whose center is at [0,0,0] and whose radius is 1.0
sphere1 = sphere_type(center=vec3([0, 0, 0]), radius=1.0)
# Initialize sphere2, whose center is at [1,1,1] and whose radius is 1.0
sphere2 = sphere_type(center=vec3([1, 1, 1]), radius=1.0)

如果定义的结构体有很多成员变量的时候,使用ti.types.struct会导致代码混乱、组织性很差,这个时候可以用一个修饰器ti.dataclass,它包装了 struct 类型

Example

@ti.dataclass
class Sphere:
    center: vec3
    radius: float

使用ti.dataclass还可以让我们在数据类中定义成员函数,从而实现 OOP 的功能

初始化

Taichi 中有很多初始化 struct 或者 dataclass 的方式,除了直接调用类型进行实例化之外,还可以用以下几种方式:

  • 按照定义的参数顺序将位置参数传递给类型
  • 利用关键字参数设置特定的成员变量
  • 未指定的成员变量将被自动设置为 0

Example

@ti.dataclass
class Ray:
    ro: vec3
    rd: vec3
    t: float

# The definition above is equivalent to
#Ray = ti.types.struct(ro=vec3, rd=vec3, t=float)
# Use positional arguments to set struct members in order
ray = Ray(vec3(0), vec3(1, 0, 0), 1.0)
# ro is set to vec3(0) and t will be set to 0
ray = Ray(vec3(0), rd=vec3(1, 0, 0))
# both ro and rd are set to vec3(0)
ray = Ray(t=1.0)
# ro is set to vec3(1), rd=vec3(0) and t=0.0
ray = Ray(1)
# All members are set to 0
ray = Ray()

类型转换

目前 Taichi 的复合类型中只有 vector matrix 支持类型转换,并且是 element-wise

Example

@ti.kernel
def foo():
    u = ti.Vector([2.3, 4.7])
    v = int(u)              # ti.Vector([2, 4])
    # If you are using ti.i32 as default_ip, this is equivalent to:
    v = ti.cast(u, ti.i32)  # ti.Vector([2, 4])

评论