【语言问题】分支语句报错以及其他

运行环境

[Taichi] version 0.8.4, llvm 10.0.0, commit 895881b5, win, python 3.8.9

问题描述

在写N-body问题时,发现多余的分支会报错,尽管在逻辑上该分支并不执行,如下:

    @ti.kernel
        def initial(self,center:ti.template(),expand_size:ti.f32,init_vel:ti.f32):
            for i in range(self.n):
                if self.n == 1:
                    self.pos[i] = center
                    for j in ti.static(range(self.dim)):
                        self.vel[i][j] = 0
                elif self.dim == 3:
                    offset = ti.Vector([ti.random(),ti.random(),ti.random()])-ti.Vector([expand_size,expand_size,expand_size])*0.5
                    originvel = ti.Vector([-offset[1],offset[0],0])*init_vel
                    self.pos[i] = center+offset
                    self.vel[i] = originvel
                """
                elif self.dim == 2:
                    offset = ti.Vector([ti.random(),ti.random()])-ti.Vector([expand_size,expand_size])*0.5
                    originvel = ti.Vector([-offset[1],offset[0]])*init_vel
                    self.pos[i] = center+offset
                    self.vel[i] = originvel
                """

其中注释部分的内容如果去掉注释,在dim为3时并不执行,但仍会报错

Traceback (most recent call last):
  File "G:\taichi\nbodyggui.py", line 29, in <module>
    bodies.initial(centerpos,expandsize,initvel)
  File "F:\python\lib\site-packages\taichi\lang\kernel_impl.py", line 859, in __call__
    return self._primal(self._kernel_owner, *args, **kwargs)
  File "F:\python\lib\site-packages\taichi\lang\kernel_impl.py", line 723, in __call__
    key = self.ensure_compiled(*args)
  File "F:\python\lib\site-packages\taichi\lang\kernel_impl.py", line 714, in ensure_compiled
    self.materialize(key=key, args=args, arg_features=arg_features)
  File "F:\python\lib\site-packages\taichi\lang\kernel_impl.py", line 518, in materialize
    taichi_kernel = _ti_core.create_kernel(taichi_ast_generator,
  File "F:\python\lib\site-packages\taichi\lang\kernel_impl.py", line 513, in taichi_ast_generator
    compiled()
  File "G:\taichi\adjustnbody.py", line 53, in initial
    self.pos[i] = center+offset
  File "F:\python\lib\site-packages\taichi\lang\common_ops.py", line 16, in __add__
    return ti.add(self, other)
  File "F:\python\lib\site-packages\taichi\lang\ops.py", line 74, in wrapped
    return a.element_wise_binary(imp_foo, b)
  File "F:\python\lib\site-packages\taichi\lang\matrix.py", line 161, in element_wise_binary
    assert self.m == other.m and self.n == other.n, f"Dimension mismatch between shapes ({self.n}, {self.m}), ({other.n}, {other.m})"
AssertionError: Dimension mismatch between shapes (3, 1), (2, 1)

代码链接

例子链接

另外,我还发现在被@ti.kernel修饰的子类函数中,super只能以python2.x的语法super(class,self)形式执行,而无法以python3.x的新式类super()语法使用,不知道这是taichi本身设计如此还是有什么问题?

对分支语句的问题尝试改写为

@ti.func
    def xvcalcu(self,dim:ti.f32,init_vel:ti.f32,expand_size:ti.f32):
        if dim == 2:
            offset = ti.Vector([ti.random(),ti.random()])-ti.Vector([expand_size,expand_size])*0.5
            originvel = ti.Vector([-offset[1],offset[0]])*init_vel
        else:
            offset = ti.Vector([ti.random(),ti.random(),ti.random()])-ti.Vector([expand_size,expand_size,expand_size])*0.5
            originvel = ti.Vector([-offset[1],offset[0],0])*init_vel
        return offset, originvel

    
                
    @ti.kernel
    def initial(self,center:ti.template(),expand_size:ti.f32,init_vel:ti.f32):
        for i in range(self.n):
            if self.n == 1:
                self.pos[i] = center
                for j in ti.static(range(self.dim)):
                    self.vel[i][j] = 0
            else:
                off, ovel = self.xvcalcu(self.dim,init_vel,expand_size)
                self.pos[i] = center+off
                self.vel[i] = ovel

但是仍然报错,问题无法解决

Traceback (most recent call last):
  File "G:\taichi\nbodyggui.py", line 29, in <module>
    bodies.initial(centerpos,expandsize,initvel)
  File "F:\python\lib\site-packages\taichi\lang\kernel_impl.py", line 859, in __call__
    return self._primal(self._kernel_owner, *args, **kwargs)
  File "F:\python\lib\site-packages\taichi\lang\kernel_impl.py", line 723, in __call__
    key = self.ensure_compiled(*args)
  File "F:\python\lib\site-packages\taichi\lang\kernel_impl.py", line 714, in ensure_compiled
    self.materialize(key=key, args=args, arg_features=arg_features)
  File "F:\python\lib\site-packages\taichi\lang\kernel_impl.py", line 518, in materialize
    taichi_kernel = _ti_core.create_kernel(taichi_ast_generator,
  File "F:\python\lib\site-packages\taichi\lang\kernel_impl.py", line 513, in taichi_ast_generator
    compiled()
  File "G:\taichi\newnbody.py", line 55, in initial
    off, ovel = self.xvcalcu(self.dim,init_vel,expand_size)
  File "F:\python\lib\site-packages\taichi\lang\kernel_impl.py", line 57, in decorated
    return fun.__call__(*args)
  File "F:\python\lib\site-packages\taichi\lang\kernel_impl.py", line 171, in __call__
    ret = self.compiled(*args)
  File "G:\taichi\newnbody.py", line 38, in xvcalcu
    return offset, originvel
UnboundLocalError: local variable 'offset' referenced before assignment

Hi @zydmtaichi, 当出现问题的时候,可以先从错误信息找一些能用的信息。

第一个帖子,错误信息已经给你了:
self.pos[i] = center+offset 这里 AssertionError: Dimension mismatch between shapes (3, 1), (2, 1)
你可以检查一下他们的维度是不是一致,跟分支语句没关系。
第二个帖子,子类函数也是可以用super的功能的。
第三个帖子,Taichi语言和Python语言还是有很多区别的。Taichi语言是

A language that is compiled, statically-typed, lexically-scoped, parallel and differentiable.
lexically-scoped 就表示变量是有作用域的,正如错误显示:

UnboundLocalError: local variable 'offset' referenced before assignment

xvcalcu 函数中的offset 变量在 if 语句结束就是undefined variable了。

你好 yupeng,
首先我确实已经反复查看了报错信息,当我调用第一个帖子中写的class中的initial方法前,该类的实例是以
bodies = adjustnbody(1000,1,3) 的方式实例化的,也就是初始化的维度是3,而且这个分支语句也限制了只有满足elif self.dim == 3条件下的语句块才会被执行,但是实际情况是如果我去掉这个语句块之后的注释,taichi似乎仍然会检查在self.dim == 2条件块下的维度匹配情况,而这显然不能匹配(初始的维度就是3),从而导致报错,这也就是我指的分支语句失灵的情况,有没有办法解决这个情况?

第二,我没有说子类函数不能使用super功能,我是说我发现在有kernel修饰的子类方法中用super,似乎只能显式写成super(class,self),而不能写成不带参数的super()这样的省略格式了(不带kernel修饰的方法就可以用,比如__init__方法),这个背后的机制是什么

第三个帖子是对于第一个帖子无法成功的一种尝试,但是确实也没成功

对于第一个帖子再说的简洁一些,就是

     @ti.kernel
            def initial(self,center:ti.template(),expand_size:ti.f32,init_vel:ti.f32):
                for i in range(self.n):
                    if self.n == 1:
                        self.pos[i] = center
                        for j in ti.static(range(self.dim)):
                            self.vel[i][j] = 0
                    elif self.dim == 3:
                        offset = ti.Vector([ti.random(),ti.random(),ti.random()])-ti.Vector([expand_size,expand_size,expand_size])*0.5
                        originvel = ti.Vector([-offset[1],offset[0],0])*init_vel
                        self.pos[i] = center+offset
                        self.vel[i] = originvel

以上是可以成功执行的

但是,请注意,以下就会报错

    @ti.kernel
            def initial(self,center:ti.template(),expand_size:ti.f32,init_vel:ti.f32):
                for i in range(self.n):
                    if self.n == 1:
                        self.pos[i] = center
                        for j in ti.static(range(self.dim)):
                            self.vel[i][j] = 0
                    elif self.dim == 3:
                        offset = ti.Vector([ti.random(),ti.random(),ti.random()])-ti.Vector([expand_size,expand_size,expand_size])*0.5
                        originvel = ti.Vector([-offset[1],offset[0],0])*init_vel
                        self.pos[i] = center+offset
                        self.vel[i] = originvel
                    
                    elif self.dim == 2:
                        offset = ti.Vector([ti.random(),ti.random()])-ti.Vector([expand_size,expand_size])*0.5
                        originvel = ti.Vector([-offset[1],offset[0]])*init_vel
                        self.pos[i] = center+offset
                        self.vel[i] = originvel

你能不能提供一下代码,我好像无法打开。

或者可以再简化一下代码?这样更容易定位错误

代码链接

两个python脚本放在同一文件夹下,运行nbodyggui即可

我又测试了一个最简单的分支结构,如下:

import taichi as ti

@ti.data_oriented
class adjustnbody:
    def __init__(self,N,dimension=2):
    
        self.dim = dimension
        self.n = N
        self.pos = ti.Vector.field(self.dim,ti.f32,self.n)
        self.vel = ti.Vector.field(self.dim,ti.f32,self.n)
    
    @ti.kernel
    def initial(self,center:ti.template(),expand_size:ti.f32,init_vel:ti.f32):
        
        for i in range(self.n):
            if self.dim == 3:
                offset = ti.Vector([ti.random(),ti.random(),ti.random()])-ti.Vector([expand_size,expand_size,expand_size])*0.5
                originvel = ti.Vector([-offset[1],offset[0],0])*init_vel
                self.pos[i] = center+offset
                self.vel[i] = originvel
            
            else:
                offset = ti.Vector([ti.random(),ti.random()])-ti.Vector([expand_size,expand_size])*0.5
                originvel = ti.Vector([-offset[1],offset[0]])*init_vel
                self.pos[i] = center+offset
                self.vel[i] = originvel
        


if __name__ == "__main__":
    ti.init(arch=ti.x64)
    bodies = adjustnbody(1000,3)
    if bodies.dim == 2:
        centerpos = ti.Vector([0.4,0.5])
    else:
        centerpos = ti.Vector([0.0,0.0,0.0])

        print(bodies.dim)
        bodies.initial(centerpos,0.3,1)
        print(bodies.pos[2])
        print(bodies.vel[4])

以上程序去掉initial方法的else分支则成功,否则就运行失败

Hi @zydmtaichi, 我写了一个更简单的版本。

import taichi as ti

ti.init(arch=ti.cpu)

dim = 3
pos = ti.Vector.field(dim, ti.f32, 10)
@ti.kernel 
def init():
    if dim == 3:
        pos[0] += ti.Vector([1,0.0, 0.0])
    elif dim == 2:
        pos[0] += ti.Vector([1.0, 0.0])

init()

这个其实不是Taichi语言的问题, 这个程序的错误涉及Taichi语言和Python语言的差异。

正如文档here 所言:

The language used in Taichi kernels and functions looks exactly like Python, yet the Taichi frontend compiler converts it into a language that is compiled, statically-typed, lexically-scoped, parallel, and differentiable.

Taichi scope的代码是提前编译好的,Python是运行时编译。所以你写的代码是一种Python的写法,但这不符合Taichi语言的规范。在提前进行编译时就会确定好pos的dimension, 也会检查 +=是否合法。

1 个赞

好的,谢谢,那请问有什么办法可以在taichi中让一个类中的方法根据维度不同进行不同的处理?

其实我第一个帖子的程序是有原作者的写法的,他是写成

offset = ti.Vector([ti.random() for j in range(self.dim)]) * off_size - ti.Vector([off_size for j in range(self.dim)]) * 0.5
self.pos[i] = pos_center + offset
self.vel[i] = ti.Vector([-offset[1], offset[0], 0.0] if self.dim == 3 else [-offset[1], offset[0]] if self.dim == 2 else [0.0 for j in range(self.dim)]) * init_speed

这样的写法在dim从最多从1变到3的时候问题还不大,但如果更多我觉得就会很不方便,比如有四五个分支或者更多分支就会变成很长的一句话,所以我试图把里面的分支写到外面来以支持更多分支的情况,但是实现不了

我也遇到类似的情况了,可以试试把要判断的语句写成

if ti.static(n==1):

这样就能在编译时确定并优化了。