太极图形课-01-Taichi编程

TaiChi编程语言

20220429161928
A DSL for Computer Graphics

  • Productivity
    Friendly learning curve
    Shorter code, higher perf.
  • Portability
    Multi-backend support
  • Performance
    Optimized for bandwidth, locality and load balancing

只用最基本的加减乘除,每个人都可以通过编程创造一个虚拟世界 ----TaiChi

安装太极编程语言

Python3 -m pip install taichi

安装成功后,直接创建python文件,将其作为包导入即可

太极示例代码:


import taichi as ti

ti.init(arch=ti.gpu)

n = 320
pixels = ti.field(dtype=float, shape=(n * 2, n))

@ti.func
def complex_sqr(z):
    return ti.Vector([z[0]**2 - z[1]**2, z[1] * z[0] * 2])

@ti.kernel
def paint(t: float):
    for i, j in pixels:  # Parallelized over all pixels
        c = ti.Vector([-0.8, ti.cos(t) * 0.2])
        z = ti.Vector([i / n - 1, j / n - 0.5]) * 2
        iterations = 0
        while z.norm() < 20 and iterations < 50:
            z = complex_sqr(z) + c
            iterations += 1
        pixels[i, j] = 1 - iterations * 0.02

gui = ti.GUI("Hello World", res=(n * 2, n))

for i in range(1000000):
    paint(i * 0.03)
    gui.set_image(pixels)
    gui.show()import taichi as ti

ti.init(arch=ti.gpu)

n = 320
pixels = ti.field(dtype=float, shape=(n * 2, n))

@ti.func
def complex_sqr(z):
    return ti.Vector([z[0]**2 - z[1]**2, z[1] * z[0] * 2])

@ti.kernel
def paint(t: float):
    for i, j in pixels:  # Parallelized over all pixels
        c = ti.Vector([-0.8, ti.cos(t) * 0.2])
        z = ti.Vector([i / n - 1, j / n - 0.5]) * 2
        iterations = 0
        while z.norm() < 20 and iterations < 50:
            z = complex_sqr(z) + c
            iterations += 1
        pixels[i, j] = 1 - iterations * 0.02

gui = ti.GUI("Hello World", res=(n * 2, n))

for i in range(1000000):
    paint(i * 0.03)
    gui.set_image(pixels)
    gui.show()

实现效果:

实时在线运行程序: https://zoo.taichi.graphics/

太极的helloworld

import taichi as ti

ti.init(arch=ti.gpu)

# 以上两行是必须要写在程序前面的,其中init()是必须的
# arch=ti.gpu表示使用GPU运算,如果不指定arch,默认是使用CPU运算

具体支持的硬件接口有:

Taichi-scope v.s. Python-scope

就是指的代码的运行环境,代码的运行是否要经过taichi的高性能运算

例子如下:

  • 仅仅在python环境下运行:

    Everything in a normal Python script is in the Python-scope

    import taichi as ti
    ti.init(arch=ti.cpu)
    def foo():
        print('hello')
    foo()
    

    上方的代码就和基本的python代码并没有什么区别

  • 使用TaiChi高性能计算环境

    Everything decorated by @ti.kernel or @ti.func is in the Taichi-scope

    import taichi as ti
    
    ti.init(arch=ti.gpu)
    
    @ti.kernel # 声明一个taichi的kernel函数
    def foo():
        print('hello')
    
    foo()
    

    当一个函数前面被@ti.kernel修饰时,这个函数就是一个taichi的kernel函数,这个函数就是一个高性能的计算函数.

太极的数据类型

分类
  • signed integers: ti.i8, ti.i16, ti.i32, ti.i64 (整型8位,16位,32位,64位)
  • unsigned integers: ti.u8, ti.u16, ti.u32, ti.u64 (无符号整型8位,16位,32位,64位)
  • floating points: ti.f32, ti.f64 (浮点型32位,64位)

具体可以看如下表格:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hlHlnBBw-1651220151861)(https://cdn.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220429105853.png)]

默认的数据类型:
- ti.f32
- ti.i32

当然,默认的数据类型是支持在init函数中修改的,具体操作如下:


ti.init(default_fp=ti.f32) # float = ti.f32
ti.init(default_fp=ti.f64) # float = ti.f64

ti.init(default_ip=ti.i32) # int = ti.i32
ti.init(default_ip=ti.i64) # int = ti.i64

注意:太极的数据类型之间存在隐式转换

也就是说,当一个变量被定义了之后,它的类型就是固定的了

可以看以下示例:如果a被定义为1,则此时的a的类型就是int32位的,那么当a再次被赋值为2.8888时,太极将会自动将2.8888转换为int32位的整数,也就是2

import taichi as ti

ti.init(arch=ti.gpu)

@ti.kernel
def foo():
    a = 1
    print("此时的a为",a)
    a = 2.8888
    print("更改之后的a为",a)
    a = 1
    b = 1.7
    c = a + b
    print("f32+i32=",c)
foo()

结果如下:

将不同的数据类型做转换: 利用ti.cast()函数
import taichi as ti
ti.init(arch=ti.cpu)
@ti.kernel
def foo():
    a = 1.7
    b = ti.cast(a, ti.i32)
    c = ti.cast(b, ti.f32)
    print("b =", b) # b = 1
    print("c =", c) # c = 1.0
foo()

结果如下:

可以看到,a由原来的float32位转换为int32位,然后再转换为float32位
复合类型

复合类型是指一个数据类型包含了多个数据类型的组合,比如:在太极中主要有1.向量2.矩阵3.结构体

- 向量: ti.Vector(n)
- 矩阵: ti.Matrix(n, m)
- 结构体: ti.Struct(...)
方法一:
import taichi as ti
ti.init(arch=ti.cpu)
vec3f = ti.types.vector(3, ti.f32) 
# 定义一个3维向量类型,其中每个元素类型是float32位
mat2f = ti.types.matrix(2, 2, ti.f32)
# 定义一个2×2矩阵类型,其中每个元素类型是float32位   
ray = ti.types.struct(ro=vec3f, rd=vec3f, l=ti.f32)
# 定义一个结构体类型来表示光线,其中包含2个三维向量来分别表示光线的起始点和光线射出的方向然后和一个浮点数来表示光线的长度
@ti.kernel
def foo():
    a = vec3f(0.0)
    print(a)  # [0.0, 0.0, 0.0]
    d = vec3f(0.0, 1.0, 0.0)
    # 使用三维向量类型来创建一个三维向量对象
    print(d)  # [0.0, 1.0, 0.0]
    B = mat2f([[1.5, 1.4], [1.3, 1.2]])
    # 使用二维矩阵类型来创建一个二维矩阵对象
    print("B =", B)  # B = [[1.5, 1.4], [1.3, 1.2]]
    r = ray(ro=a, rd=d, l=1)
    # 使用结构体类型来创建一个结构体对象
    print("r.ro =", r.ro) # r.ro = [0.0, 0.0, 0.0]
    print("r.rd =", r.rd)  # r.rd = [0.0, 1.0, 0.0]
foo()
方式二:
mport taichi as ti
ti.init(arch=ti.cpu)
@ti.kernel
def foo():
    a = ti.Vector([0.0, 0.0, 0.0])
    # 直接使用ti.Vector()来创建一个三维向量对象
    print(a)  # [0.0, 0.0, 0.0]
    d = ti.Vector([0.0, 1.0, 0.0])
    print(d)  # [0.0, 1.0, 0.0]
    B = ti.Matrix([[1.5, 1.4], [1.3, 1.2]])
    # 直接使用ti.Matrix()来创建一个二维矩阵对象
    print("B =", B)  # B = [[1.5, 1.4], [1.3, 1.2]]
    r = ti.Struct(v1=a, v2=d, l=1)
    print("r.v1 =", r.v1)  # r.v1 = [0.0, 0.0, 0.0]
    print("r.v2 =", r.v2)  # r.v2 = [0.0, 1.0, 0.0]
foo()

可以直接利用ti.Vectorti.Matrix来定义向量和矩阵,而ti.Struct则是用来定义结构体

ti.field ---- a global N-d array of elements

• global: can be read/written from both the Taichi-scope and the Python-scope

• N-d: (Scalar: N=0), (Vector: N=1), (Matrix: N=2), (N = 3, 4, 5, …)

• elements: scalar, vector, matrix, struct

结合具体的代码来理解吧:

import taichi as ti
ti.init(arch=ti.cpu)
pixels = ti.field(dtype=float, shape=(16, 8))
pixels[1, 2] = 42.0
print(pixels[1, 2])  # 42.0

这里定义的是一个16×8的field,也就是说这个field中包含了16×8个元素,每个元素的类型一个float元素

我的理解是这个样的:

20220429120444

import taichi as ti
ti.init(arch=ti.cpu)
vf = ti.Vector.field(3, ti.f32, shape=4)
@ti.kernel
def foo():
    v = ti.Vector([1, 2, 3])
    vf[0] = v
    print(vf[0])  # [1.0, 2.0, 3.0]
foo()

这个是定义的一个4×1大小的field,也就是说这个field中包含了4个元素,每个元素都是一个3维向量,每个三维向量中的元素类型是float32

我的理解是这样:

特别的:access a zero-d field using [None]

例如:


zero_d_scalar = ti.field(ti.f32, shape=())
zero_d_scalar[None] = 1.5

zero_d_vec = ti.Vector.field(2, ti.f32, shape=())
zero_d_vec[None] = ti.Vector([2.5, 2.6])

可以再试着理解一下这几个例子,应该就可以理解field了

•“3D gravitational field in a 256x256x128 room”

gravitational_field = ti.Vector.field(n = 3,dtype=ti.f32,shape=(256,256,128))

•“2D strain-tensor field in a 64x64 grid”

strain_tensor_field = ti.Matrix.field(n = 2,m = 2,dtype=ti.f32, shape=(64,64))

•“a global scalar that I want to access in a Taichi kernel”

global_scalar = ti.field(dtype=ti.f32, shape=())

太极的计算核

串行与并行

首先,假想一个问题:

可以明显的看到,虽然CPU的单个核心的能力比较强,但是,在人多力量大面前,也是自愧不如的,因为图形学大部分计算都是相同的,所以可以去让每个点对应的计算去并行进行,从而提高计算速度

kernel == cuda _global_

我现在理解到的kernel就是整个太极优化的核心部分,在这个kernel下面的函数中,如果存在for循环的话,而且该for循环是位于第一层,Taichi会将其在编译时,将其分给多个并行的核心去执行该循环,从而提高计算速度,但是如果该循环不是在第一层,那么,它将仍然会以串行的方式去执行,比如下面的示例代码:

@ti.kernel
def fill():
    for i in range(10): 
        # 在第一层,所以该层的所有操作均会被并行执行
        # 可以理解为,本次总共有10个重复的大操作
        # 所以,taichi将会把这10个分成10份,分别交给1个gpu核心去运算
        x[i] += i
        s = 0
        for j in range(5): 
            # 在第二层,因为这是已经被分配到核心的具体任务了
            # 所以,这里的操作只会被一个核心去执行
            # 因此,这里的操作只能是串行执行的,而不是并行的
            s += j
        y[i] = s

同时,应该注意,如果在一个kernel下的函数有多个第一层for循环,那么每个第一层for循环都是会被并行执行的
但是,在每个第一层for循环之间,它们是串行执行的,可以看一下代码:

import taichi as ti
ti.init(arch=ti.gpu)

a = ti.field(ti.f32, shape=())
@ti.kernel
def foo():
    for i in range(10):
        # 第一层,分配10个核心去执行
        a[None] += 1
    # 等结束之后,回收10个核心
    print("第一个for循环后结果:",a[None])
    for i in range(10):
        # 第一层,再分配这10个核心去执行
        a[None] += 2
    # 等结束之后,回收10个核心
    print("第二个for循环后结果:",a[None])
    for i in range(10):
        # 第一层,再再分配这10个核心去执行
        print(a[None])
    # 等结束之后,回收10个核心
    print("第三个for循环后结果:",a[None])

foo()

输出结果如图:

可以很清楚的看到同是第一层的for之间是串行执行的

我们将上述代码做如下修改:

import taichi as ti
ti.init(arch=ti.gpu)

a = ti.field(ti.f32, shape=())
@ti.kernel
def foo():
    for i in range(100):
        # 第一层,分配10个核心分别去执行10个任务,每完成一次,就输出一次
        # 可以看到,这10个任务是并行的,
        # 也就是说,输出的结果将会是无序的
        a[None] += 1
        print("第一次for的第",i,"次加法,结果为:",a[None])
    print("第一个for循环后结果:",a[None])
    for i in range(10):
        a[None] += 2
    print("第二个for循环后结果:",a[None])
    for i in range(10):
        print(a[None])
    print("第三个for循环后结果:",a[None])
    # 但最后输出的结果肯定会是正确的
foo()

执行结果如图:

结果已经很明显了,第一个for循环的结果是无序的,也就证明了第一层for中的每几个重复的操作是并行执行的

理解这个之后,如果我们想把第二层的for进行并行执行,那我们可以进行如下操作:

将想要并行的for写进kernel函数中,然后在kernel函数中进行并行执行,把第一层的for留在外面

注意,由于第一层for是并行的,所以,我们无法在某个核心中将其break掉

这里还有一个问题,就是并行虽然可以提高速度,但是,对于数据我们无法保证它们是同步的,也就是说,可能每个核心在将要做任务时,现在已经有核心在做了,这明显就会引起重复的任务,导致最后结果不正确(其实,这个和操作系统的进程之间的关系有点类似)

拿下面举例:
加法操作为: a[None] = a[None] + 1
这个可以看成3部分:
第一,取得这个原始值
第二,对其进行加一操作
第三,写回对应的地址

比如:

import taichi as ti
ti.init(arch=ti.gpu)

a = ti.field(ti.f32, shape=())
@ti.kernel
def foo():
    for i in range(10):
        a[None] = a[None] + 1
        print("这是第",i,"次的结果:",a[None])
    print("这是最终的结果:",a[None])
foo()

可以看到每个核心都只做了一次最初始数的操作,也就是说,每个核心并没有意识到其实已经有人在做了,他自己又拿最初的数做一遍,所以结果肯定是不正确的

但,如何解决呢,我们可以定义原子操作,也就是将其三步变成一步,这样,当一个核心已经开始进入原来逻辑的第一步时,其它核心就不可以操作,这样就保证了原子操作的正确性

import taichi as ti
ti.init(arch=ti.gpu)

a = ti.field(ti.f32, shape=())
@ti.kernel
def foo():
    for i in range(100):
        a[None] += 1
        print("这是第",i,"次的结果:",a[None])
    print("这是最终的结果:",a[None])
foo()

可以看到,结果是正确的,同时,输出也是无序的,说明,原子操作之外的操作,仍是并行执行的

比如最后一行可以解释为:当第63号核心做完它的第一个原子操作时,再想回头输出看看结果时,发现其它核心已经在它想看的这个间隙中,将结果做完了,所以会输出100

以上就是串行并行原子操作等的解释了

然后就是,kernel还要注意几点:

第一,传入的参数要指定类型

import taichi as ti
ti.init(arch=ti.cpu)
@ti.kernel
def my_kernel(x: ti.i32, y: ti.f32):
    print(x + y)
my_kernel(2, 3.3)  # 5.3

第二,向量要分解成单个再传入

import taichi as ti
ti.init(arch=ti.cpu)
@ti.kernel
def bad_kernel(v: ti.Vector):# 错误
    ...
@ti.kernel
def good_kernel(vx: ti.f32, vy: ti.f32):#正确
    v = ti.Vector([vx, vy])
    ...

第三,与c++类似,它不会对传入的参数做值的变化

import taichi as ti
ti.init(arch=ti.cpu)
@ti.kernel
def foo(x: ti.i32):
    x = x + 1
    print("x in foo:", x) # 101
x = 100
foo(x)
print("x outside foo:", x) # 100

如果想要可以变化,就和c++一样,使用全局变量:在这里是ti.field()

func

可以基本理解为宏定义,就是将重复的代码部分写进一个函数,当调用函数时,相当于把代码再还原回去

所以,该函数也就不能支持递归操作了

import taichi as ti
ti.init(arch=ti.cpu)
@ti.func
def my_func(x):
    x = x + 1 
    print("x in my func:", x)  # 234
@ti.kernel
def my_kernel():
    x = 233
    my_func(x)
    print("x outside my func:", x)  # 233
my_kernel()

kernel func python 之间调用关系

ops

20220429160714

可视化 gui

先来看一下效果吧

GPU下运行:

CPU下运行:

上面的FPS说明了一切

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
好的,以下是一个简单的欧拉视角的流体仿真程序,使用了Taichi编程语言。代码中包含了注释,希望能够帮助您理解。 ```python import taichi as ti ti.init(arch=ti.gpu) # 初始化Taichi,指定使用GPU # 定义仿真参数 dim = 2 # 维度 res = 128 # 分辨率 dt = 1e-4 # 时间步长 viscosity = 0.01 # 粘性系数 gravity = ti.Vector([0.0, -9.8]) # 重力 # 声明变量 vel = ti.Vector(dim, dt=ti.f32, shape=(res, res)) # 速度场 vel_new = ti.Vector(dim, dt=ti.f32, shape=(res, res)) # 新的速度场 density = ti.var(dt=ti.f32, shape=(res, res)) # 密度场 density_new = ti.var(dt=ti.f32, shape=(res, res)) # 新的密度场 # 定义函数,用于计算速度场的散度 @ti.kernel def divergence(): for i, j in vel: vel_new[i, j] = vel[i, j] + dt * (-gravity + viscosity * ti.Matrix([[0,-1],[1,0]]) @ vel.grad[i, j]) if i > 0 and j > 0 and i < res-1 and j < res-1: density_new[i, j] = density[i, j] + dt * (-vel_new[i, j].dot(ti.Vector([density.grad[i, j][0], density.grad[i, j][1]]))) # 交换速度场和新的速度场,密度场和新的密度场 vel, vel_new = vel_new, vel density, density_new = density_new, density # 主循环 for step in range(1000): divergence() # 计算速度场的散度 if step % 50 == 0: ti.imshow(density.to_numpy(), cmap='gray') # 显示密度场 ``` 上述代码实现的是一个简单的二维流体仿真,使用了欧拉视角的方法。程序中主要包含了以下几个部分: 1. 初始化Taichi,并设置使用GPU加速。 2. 定义了一些仿真参数,包括维度、分辨率、时间步长、粘性系数和重力。 3. 声明了一些用于存储速度场、密度场的变量,以及用于更新速度场、密度场的变量。 4. 定义了一个计算速度场的散度的函数,其中使用了Taichi的自动求导功能来计算速度场的梯度,并使用旋度算子计算速度场的散度。 5. 在主循环中调用计算速度场的散度的函数,并在每50个时间步长之后显示密度场。 需要注意的是,上述代码只是一个非常简单的流体仿真程序,还有很多方面需要进一步完善,如边界条件、数值稳定性等。但是这个程序可以作为入门级别的流体仿真教程,帮助初学者理解欧拉视角的流体仿真原理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CX__CS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值