Question about updating temporary variable(ti.Vector)

Question about updating temporary global variable

Env

in case

[Taichi] mode=release

[Taichi] version 0.6.37, llvm 10.0.0, commit e0ac1d86, osx, python 3.8.3

[Taichi] Starting on arch=x64

In short

I want to access temporary variable(say init by ti.Vector() ) in taichi scope. However, the results turns out that in taichi scope , program could only capture the initial value of the temporary variable if the variable is not the input of this taichi scope function, but in py scope it could.

To be clear, here we have this script:

import taichi as ti
import math

ti.init(ti.cpu, debug=True)

@ti.data_oriented
class trsfrm:
    def __init__(self, translation=ti.Vector([0.0, 0.0]), orientation=0.0, localscale=1.0):
        '''
        class variable init by ti.Vector
        :param translation:
        :param orientation:
        :param localscale:
        '''
        self.translation = translation
        self.orientation = orientation % (2 * math.pi)
        self.localScale = localscale

    def __repr__(self):
        return '{} ( Trsln : {}, Ornttn: {}, lclScl: {})'.format(
            self.__class__.__name__,
            self.translation,
            self.orientation,
            self.localScale)

@ti.data_oriented
class trsfrm_field:
    def __init__(self):
        '''
        class member init by ti.Vector.field
        '''
        self.translation = ti.Vector.field(2, dtype=ti.f32, shape=[1])

        # self.orientation = orientation % (2 * math.pi)
        # self.localScale = localscale

    def __repr__(self):
        return '{} ( Trsln : {})'.format(
            self.__class__.__name__,
            self.translation,
            )


@ti.kernel
def kern_test_with_input(t_1 : ti.template(), t_2 : ti.template()):
    print("kern with input t1: ", t_1)
    print("kern with input t2: ", t_2[0])

@ti.kernel
def kern_test_without_input():
    print("kern without input t1: ", t1.translation)
    print("kern without input t2: ", t2.translation[0])



@ti.kernel
def kern_add(t:ti.template()):
    t[0] += ti.Vector([2.0, 2.0])
    # print("kern add field", t[0])

t1 = trsfrm()
t2 = trsfrm_field()

while (True):

    t1.translation = t1.translation + ti.Vector([2.0, 2.0])
    print(t1)
    print("py scope print t1: ",t1.translation)

    # increment t2
    kern_add(t2.translation)

    kern_test_without_input()
    # can not capture the incremented t1
    kern_test_with_input(t1.translation, t2.translation)
    print(" ")

What’s the program?

Bascially, the program has two forms of class trsfm, one(trsfm) init with ti.Vector, another(trsfrm_field) with ti.Vector.field().

My current implementation is class trsfrm, what I wanna do is to increment the translation member of class trsfrm and capture the incremented class trsfrm in taichi scope.

However, in kern_test_without_input it could only capture the initial value( keep printing [2.0, 2.0] instead of incrementing itself ). The funny thing to me is that: in py scope the t1 still updates itself . While in kern_test_input, with t1 as input it can capture the increasing value.

The truth is, the trsfrm_field no matter without or with input, the taichi scope could always capture the increasement.

The program output is :

trsfrm ( Trsln : [2. 2.], Ornttn: 0.0, lclScl: 1.0)
py scope print t1:  [2. 2.]
[Taichi] materializing...
kern without input t1:  [2.000000, 2.000000]
kern without input t2:  [2.000000, 2.000000]
kern with input t1:  [2.000000, 2.000000]
kern with input t2:  [2.000000, 2.000000]
 
trsfrm ( Trsln : [4. 4.], Ornttn: 0.0, lclScl: 1.0)
py scope print t1:  [4. 4.]
kern without input t1:  [2.000000, 2.000000]
kern without input t2:  [4.000000, 4.000000]
kern with input t1:  [4.000000, 4.000000]
kern with input t2:  [4.000000, 4.000000]
 
trsfrm ( Trsln : [6. 6.], Ornttn: 0.0, lclScl: 1.0)
py scope print t1:  [6. 6.]
kern without input t1:  [2.000000, 2.000000]
kern without input t2:  [6.000000, 6.000000]
kern with input t1:  [6.000000, 6.000000]
kern with input t2:  [6.000000, 6.000000]

The kern without input t1 is always the same. Due to some resons I expect it to increase as well.

Sry about the wording

So The Question:

  1. Is there an elegant way to capture the ti.Vector change in taichi global scope?

  2. p.s. why the kern without input t1 can not capture the t1 increasement? Is this a bug or meant to be like this ?

  3. Take as input would cause performance to drop. This phenomenon can not be easily captured here in my repo, take the transform as input would cause fps to drop from 15 fps to 8fps, with the same amout of calculation I think.

    So why?

Why asking this?

I want to implement a transform class for collider in euler based fluid simulation in order to make the collider rotate or translate. Here is the repo link.

The circle(collider) stands still if I do not make the transform.rotation as the function input in rendering function. (currently I access the )

Currently my walkaround is either

  • take as kernel function(render) input
  • init with ti.field

But personally, I am not a big fan of capturing the increasement by adding t1 as input because in that way it may take undefined multiple number of colliders as input. And probably due to this, although the ball can rotate, the fps drops drastically(from 15fps -> 8fps). From the experience, it should not happen since the function did not change that much whether with or without the input.

Also init the transform member with ti.field is not that feasible to me because ti.field cannot be init after materialize or start an kernel function. In this way we somehow cannot trivially add new collider with transform as member to the scene.

So is there any walkaround for this ? :face_with_head_bandage:

Hope my statement is clear and clean…

Is there an elegant way to capture the ti.Vector change in taichi global scope?

Please use ti.Vector.field if you wish to change its value at run-time, otherwise they’re compile-time constant freezed at their initial value.

p.s. why the kern without input t1 can not capture the t1 increasement? Is this a bug or meant to be like this ?

It’s actually meant to be. Only fields can be captured as non-constant values in Taichi-scope.

Take as input would cause performance to drop. This phenomenon can not be easily captured here in my repo, take the transform as input would cause fps to drop from 15 fps to 8fps, with the same amout of calculation I think.

By using ti.template(), Taichi will compile a new kernel for each different input value. See https://taichi.readthedocs.io/en/stable/compilation.html.
So you’re actually triggering JIT compiler again and again in each frame by keep modifying t1, instead of invoking just the kernel.
To make JIT compiler only triggered once:

  1. Use a field instead to pass arguments:
t1 = ti.Vector.field(, ())

def func():
   print(t1[None])

while True:
  t1[None] = t1[None] + ...
  func()
  1. Use several scalar arguments with type-hints:
def func(t1_x: float, t1_y: float):  # use `int` if t1 is always integer vector
  t1 = ti.Vector([t1_x, t1_y])

while True:
  t1 += ...
  func(t1.x, t1.y)
1 Like

OK thanks for the detailed reply, which is helpful !

BTW, I wonder whether Taichi would support creating new ti.field after materialization ? I noticed it has been somehow discussed in issue here. Does it end up well and would this feature be released soon ?

Also wonder whether placing Taichi class(decorated by @ti.data_oriented) as input (say def kern_func(my_class:ti.template())) would trigger JIT compiler again and again?
I guess it would not because I do not experience huge performance cliff. But I just want to make sure.

whether placing Taichi class(decorated by @ti.data_oriented) as input (say def kern_func(my_class:ti.template())) would trigger JIT compiler again and again?

It won’t, in fact ti.template() will only trigger JIT compiler when the __hash__ of class is changed.

OK I see
56E7DD7EB05BF28E911CB97A5F14AD47