importtaichiastiquality=1# Use a larger value for higher-res simulationsn_particles,n_grid=9000*quality**2,128*qualitydx,inv_dx=1/n_grid,float(n_grid)dt=1e-4/qualityp_vol,p_rho=(dx*0.5)**2,1p_mass=p_vol*p_rhoE,nu=0.1e4,0.2# Young's modulus and Poisson's ratiomu_0,lambda_0=E/(2*(1+nu)),E*nu/((1+nu)*(1-2*nu))# Lame parametersx=ti.Vector(2,dt=ti.f32,shape=n_particles)# positionv=ti.Vector(2,dt=ti.f32,shape=n_particles)# velocityC=ti.Matrix(2,2,dt=ti.f32,shape=n_particles)# affine velocity fieldF=ti.Matrix(2,2,dt=ti.f32,shape=n_particles)# deformation gradientmaterial=ti.var(dt=ti.i32,shape=n_particles)# material idJp=ti.var(dt=ti.f32,shape=n_particles)# plastic deformationgrid_v=ti.Vector(2,dt=ti.f32,shape=(n_grid,n_grid))# grid node momemtum/velocitygrid_m=ti.var(dt=ti.f32,shape=(n_grid,n_grid))# grid node massti.cfg.arch=ti.cuda# Try to run on GPU@ti.kerneldefsubstep():fori,jinti.ndrange(n_grid,n_grid):grid_v[i,j]=[0,0]grid_m[i,j]=0forpinrange(n_particles):# Particle state update and scatter to grid (P2G)base=(x[p]*inv_dx-0.5).cast(int)fx=x[p]*inv_dx-base.cast(float)# Quadratic kernels [http://mpm.graphics Eqn. 123, with x=fx, fx-1,fx-2]w=[0.5*ti.sqr(1.5-fx),0.75-ti.sqr(fx-1),0.5*ti.sqr(fx-0.5)]F[p]=(ti.Matrix.identity(ti.f32,2)+dt*C[p])@F[p]# deformation gradient updateh=ti.exp(10*(1.0-Jp[p]))# Hardening coefficient: snow gets harder when compressedifmaterial[p]==1:# jelly, make it softerh=0.3mu,la=mu_0*h,lambda_0*hifmaterial[p]==0:# liquidmu=0.0U,sig,V=ti.svd(F[p])J=1.0fordinti.static(range(2)):new_sig=sig[d,d]ifmaterial[p]==2:# Snownew_sig=min(max(sig[d,d],1-2.5e-2),1+4.5e-3)# PlasticityJp[p]*=sig[d,d]/new_sigsig[d,d]=new_sigJ*=new_sigifmaterial[p]==0:# Reset deformation gradient to avoid numerical instabilityF[p]=ti.Matrix.identity(ti.f32,2)*ti.sqrt(J)elifmaterial[p]==2:F[p]=U@sig@V.T()# Reconstruct elastic deformation gradient after plasticitystress=2*mu*(F[p]-U@V.T())@F[p].T()+ti.Matrix.identity(ti.f32,2)*la*J*(J-1)stress=(-dt*p_vol*4*inv_dx*inv_dx)*stressaffine=stress+p_mass*C[p]fori,jinti.static(ti.ndrange(3,3)):# Loop over 3x3 grid node neighborhoodoffset=ti.Vector([i,j])dpos=(offset.cast(float)-fx)*dxweight=w[i][0]*w[j][1]grid_v[base+offset]+=weight*(p_mass*v[p]+affine@dpos)grid_m[base+offset]+=weight*p_massfori,jinti.ndrange(n_grid,n_grid):ifgrid_m[i,j]>0:# No need for epsilon heregrid_v[i,j]=(1/grid_m[i,j])*grid_v[i,j]# Momentum to velocitygrid_v[i,j][1]-=dt*50# gravityifi<3andgrid_v[i,j][0]<0:grid_v[i,j][0]=0# Boundary conditionsifi>n_grid-3andgrid_v[i,j][0]>0:grid_v[i,j][0]=0ifj<3andgrid_v[i,j][1]<0:grid_v[i,j][1]=0ifj>n_grid-3andgrid_v[i,j][1]>0:grid_v[i,j][1]=0forpinrange(n_particles):# grid to particle (G2P)base=(x[p]*inv_dx-0.5).cast(int)fx=x[p]*inv_dx-base.cast(float)w=[0.5*ti.sqr(1.5-fx),0.75-ti.sqr(fx-1.0),0.5*ti.sqr(fx-0.5)]new_v=ti.Vector.zero(ti.f32,2)new_C=ti.Matrix.zero(ti.f32,2,2)fori,jinti.static(ti.ndrange(3,3)):# loop over 3x3 grid node neighborhooddpos=ti.Vector([i,j]).cast(float)-fxg_v=grid_v[base+ti.Vector([i,j])]weight=w[i][0]*w[j][1]new_v+=weight*g_vnew_C+=4*inv_dx*weight*ti.outer_product(g_v,dpos)v[p],C[p]=new_v,new_Cx[p]+=dt*v[p]# advectionimportrandomgroup_size=n_particles//3foriinrange(n_particles):x[i]=[random.random()*0.2+0.3+0.10*(i//group_size),random.random()*0.2+0.05+0.32*(i//group_size)]material[i]=i//group_size# 0: fluid 1: jelly 2: snowv[i]=[0,0]F[i]=[[1,0],[0,1]]Jp[i]=1importnumpyasnpgui=ti.GUI("Taichi MLS-MPM-99",res=512,background_color=0x112F41)forframeinrange(20000):forsinrange(int(2e-3//dt)):substep()colors=np.array([0x068587,0xED553B,0xEEEEF0],dtype=np.uint32)gui.circles(x.to_numpy(),radius=1.5,color=colors[material.to_numpy()])gui.show()# Change to gui.show(f'{frame:06d}.png') to write images to disk
Material Point Method (MPM)是一种模拟连续介质的方法,最早被Sulsky等人在1995年发明[Application of a particle-in-cell method to solid mechanics]。由于在计算中同时使用了Lagrangian particle和Eulerian grid,他被归类到Hybrid Lagrangian-Eulerian Simulation Method。这类方法最早可以追溯到Particle-in-Cell (PIC, The particle-in-cell method for numerical solution of problems in fluid dynamics, 1963) 和Fluid Implicit Particles (FLIP, A method for adaptively zoned, particle-in-cell calculations of fluid flows in two dimensions,1986)。熟悉有限元的同学可以把MPM里面的grid nodes对应到FEM里面的DOFs, 把MPM里面的particles对应到FEM里面的quadrature points。和FEM里使用显式网格测策略不同,作为一种Element-Free Galerkin(EFG)法,MPM里面并没有显式的Elements和Lagrangian grid,只有能够随意移动的粒子作为quadrature points。这样的特性使得它非常适合处理大形变,而其背景网格带来的自动碰撞处理、多材料耦合。由于离散化出自weak form,这使得MPM的physical accuracy有了保证。HCm快充网络
2017年大四暑假,我在宾夕法尼亚大学visit蒋老师。我们设计了一个新算法,Moving Least Squares MPM (MLS-MPM)。MLS-MPM不仅性能比之前的state of the art高两倍,而且代码短了很多,非常容易实现。为了加速我们的实验,让MPM跑的快一点,我花了点时间手写SIMD intrinsics,把它加速了6倍。在这个基础上,MLS-MPM在算法上的改进还能进一步提高两倍的性能。HCm快充网络
在费城一个烟雨蒙蒙的晚上,我们几个作者(Yu Fang, Ziheng Ge, Ziyin Qu)集中在蒋老师的实验室,经过一系列讨论,我突发奇想:既然APIC的affine velocity field和MPM里面的deformation gradient update需要的都是velocity gradients,那能不能用moving least squares统一两种离散化?我快速的实现了一下,发现确实可行,但是问题是这个新离散化理论上是否有原来的MPM的精度?那天晚上我们去吃了顿中餐,一路上一直在讨论这个问题。吃饱了饭,晚上我们一直工作到凌晨3点,当时我已经困得不行了,只记得蒋老师突然兴奋的说了一句“weak form推出来了!算法是weak-form consistent的!” 我看了一眼蒋老师草稿纸上的推导,平均每个符号有3个上下标(\alpha, \beta, i, j, k,下划线,双下划线,等等),似懂非懂,只知道有了这张草稿,算法的物理正确性算是有了保证。后来我花了两整天的时间才弄懂蒋老师那晚上推出来的公式。(蒋老师是中科大少年班毕业,只比我大四五岁,却已经当了一两年教授了。相比之下我那时候还没开始读PhD...)HCm快充网络
在此期间另外一个值得一提的工作是和Dartmouth College的朱博教授、UWisconsin-Madison的Haixiang Liu和Eftychios Sifakis做的Giga-Voxel topology optimization(1,040,875,347个体素的拓扑优化)。后来这篇文章发到了SIGGRAPH Asia 2018,我们成功证明了通过end-to-end codesign出来的solver,使得一台机器能够发挥100台机器的效果。经过这样的一个项目,我从朱博、Eftychios还有Haixiang身上学到很多。这其中的故事值得有空的时候单独写一篇文章,这里就不多说了。HCm快充网络
由于种种原因,我的Ph.D.第一年很难说过得有多愉快,不过还是发了6篇paper。在“一年内发出的ACM Transactions on Graphics的数量”这个指标上,我感觉可能很难再刷新自己的记录了。“既然已经发了足够的SIGGRAPH,不需要再追求数量,现在也没有太大毕业压力,不如用剩下来的时间和科研自由做点真正有意义的事情,比如说提高整个社区的生产力。” 我这么想。HCm快充网络
比如说,计算机图形学的算法大多用C++实现。一些组里的代码库出现了编译一把要一个多小时的情况。由于大量使用templates (比如2D/3D simulator用template一起写,再对float/double实例化),新来的同学都表示上手非常困难,很多时候需要debug compilation,因为C++模板的compilation error太难懂了。甚至有同学表示“通过编译就是成功的一半”。而就是同样这一批同学,可能毕业的时候已经成了C++高手,满口“TMP“, ”SFINAE”,代码必须要C++17才能编译,编译的时候compiler疼的嗷嗷叫,甚至出现g++ out of memory等等。新来的同学又要继续要承受modern C++和各种over design的折磨。这种形式的知识继承,是相对比较低效的。HCm快充网络
我就曾今接手过一个MPM代码库,有一次在一台“只”有32GB内存的机器上开4个线程编译。由于改了一个底层的header,估计要一个小时才能编译完,我决定先去吃午饭了。去楼下中东餐车了排半天,买了个$7还送饮料的lamb over rice(某种“羊肉盖浇饭”?),吃完回实验室已经快一个小时以后了。我本以为代码应该编译好了可以运行了,结果看到屏幕的那一刻我内心是沮丧的:编译了20分钟的时候,机器out of memory了,只好把memory swap到hard disk,现在OS已经卡的没法操作了。这是一个宝贵的教训:要编译那份代码的时候,如果我只有32GB的内存,那我只敢开3个线程编译,即使CPU有8个cores。HCm快充网络
为了解决计算机图形学研究对性能的追求以及生产力低下的问题,我决定重新设计编程语言。这是一个大胆的决定,因为虽然我之前花了很多时间做low-level performance engineering,但是真让我写个编译器我还从没干过。从2019年1月起,我一直在做Taichi programming language (这里非常感谢我现在的两位老板Fredo和Bill给我这样的自由) ,这个结果发表在SIGGRAPH Asia 2019上。HCm快充网络
这个项目本身还算挺enjoyable的,让我学到了不少新东西。一开始做这个项目的时候刚换组,新的组还没有电脑用,我就work from home了一个月。我还记得有一次我去了一趟中国城买菜以后连续在家写代码5天没出门,最后实在受不了没人说话去实验室找同学聊天,学长韬哥都说我身上“长蘑菇”了。HCm快充网络
Comments about Video: The video would be a great place to provide an overview and a big picture of the entire framework, and could also help the reader understand details that were not adequately explained in the paper. Unfortunately, this was not done, the video is extremely short, and only shows some examples.
当然缺点也不是没有,比如说Taichi“寄人篱下”以后有了一些限制,首先是语法必须parsable by the Python parser,不然根本拿不到AST;其次是有些时候性能会受到Python的限制;另外就是作为编译性、静态类型、lexically scoped的Taichi语言和Python本身有一定差别,可能会给习惯了Python思维方式的新用户一定的误导,使得一些bug难以被发现。HCm快充网络
不过这些问题后来都得到了还算不错的解决,用户们也可以很容易的“import taichi as ti”了,就像文章开头的99行代码一样。回过头来看,总体上“Taichi假装成Python”这个决定还是利大于弊的。HCm快充网络
Taichi论文摘要:(SIGGRAPH Asia 2019) 三维体积数据通常具有空间稀疏性。为了利用这种性质,计算机图形学社区开发了层级体素稀疏数据结构,如SPGrid、VDB和八叉树等。但是,由于其内在复杂性和额外开销,开发、应用这些高性能数据结构有很多挑战。我们提出Taichi,一个新的面向(稀疏)数据的编程语言,大大降低了空间稀疏数据结构的开发、使用成本。由于Taichi实现了算法和数据结构的解耦,使用者可以快速尝试不同数据结构,以在特定问题和体系结构上找到最优数据结构。语言前端提供给用户易用的接口,使得用户可以以访问稠密数据结构的方式访问稀疏数据结构,大大提高了代码可读性和生产力。Taichi编译器使用对数据结构的语义和下标分析来优化程序的局部性,移除多余数据结构遍历,以及进行自动内存管理和并行化、向量化。在x86_64和CUDA体系结构上,只需要1/10的代码,Taichi程序就能比手动优化的稀疏计算基准程序快4.55倍,测试程序包括物质点法、有限元模拟、多重网格泊松方程求解,真实感渲染,和3D稀疏卷积神经网络等。项目主页和代码:https://github.com/yuanming-hu/taichiHCm快充网络