Python プログラミング言語の誕生以来、その中心となる哲学は、コードの読みやすさと単純さを最大限に高めることでした。 Python の読みやすさとシンプルさの追求は、ほとんど狂気の沙汰です。これは 1 つの事実から確認できます。Python システムのルート ディレクトリに「import this」コマンドを入力して Enter キーを押すと、すぐに小さな英語の詩が表示されます。これを中国語に翻訳すると、おおよそ次のような意味になります。
"美しいことは醜いことよりも優れており、明示的なことは暗黙的なことよりも優れています。 単純なほうが複雑なより優れており、複雑なほうが複雑なほうが優れています。フラットなほうがネストされたよりも優れており、スパースなほうがより優れています。 読みやすさは非常に重要です...」単純さは複雑さより優れており、読みやすさは非常に重要です。 Python が実際にこれらの目標を達成するのに非常に成功していることは疑いの余地がありません。Python は学習するのに断然最もユーザーフレンドリーな言語であり、通常の Python プログラムは通常、同等の C コードよりも 5 ~ 10 倍短いです。残念ながら、そこには落とし穴があります: Python の単純さはパフォーマンスを犠牲にしています! 実際、Python プログラムは C プログラムよりも 10 ~ 100 倍遅いです。したがって、速度と単純さの間には恒久的なトレードオフが存在し、どのプログラミング言語も両方を兼ね備えることは不可能であると考えられます。 しかし、心配しないでください。すべての希望が失われるわけではありません。 Taichi は両方の長所を提供しますTaichi プログラミング言語は、汎用の高性能コンピューティングをサポートする構造で Python プログラミング言語を拡張する試みです。マルチコア CPU 機能や、さらに重要な GPU パフォーマンスなど、コンピューターのすべてのコンピューティング能力を活用しながら、Python へのシームレスな埋め込みをサポートします。 この記事では、Taichi を使用して作成したサンプル プログラムを紹介します。このプログラムは GPU を使用して、球体上に落ちる布片のリアルタイムの物理シミュレーションを実行し、同時に結果をレンダリングします。 リアルタイム GPU 物理シミュレーターを作成するのは簡単な作業ではありませんが、このルーチンを実装する Taichi ソース コードは非常にシンプルです。この記事の残りの部分では、実装全体を説明して、Taichi が提供するものと、それがいかに強力で使いやすいかを理解できるようにします。 始める前に、このプログラムが何行のコードで構成されているか推測したほうがよいでしょう。もちろん、答えは記事の最後にあります。 アルゴリズムの概要私たちのプログラムは、一枚の布を質量バネシステムとしてモデル化します。より具体的には、このクロスを点塊の N×N グリッドとして表し、隣接する点がバネで接続されています。スタンフォード大学の Matthew Fisher が提供した以下の図は、この構造を示しています。 この質量バネ システムの動きは、次の 4 つの要素の影響を受けます:import taichi as ti
ti.init(arch=ti.cuda)
ti.init(arch=ti.cpu)
N = 128 x = ti.Vector.field(3, float, (N, N)) v = ti.Vector.field(3, float, (N, N))
ball_radius = 0.2 ball_center = ti.Vector.field(3, float, (1,))
def init_scene(): for i, j in ti.ndrange(N, N): x[i, j] = ti.Vector([i * cell_size, j * cell_size / ti.sqrt(2), (N - j) * cell_size / ti.sqrt(2)]) ball_center[0] = ti.Vector([0.5, -0.5, 0.0])
在每个时间步中,我们的程序都会模拟影响布料运动的4个因素:重力、弹簧内力、阻尼和与红球的碰撞。其中,重力是最容易处理的。
下面是实现这一点的代码:
@ti.kernel def step(): for i in ti.grouped(v): v[i].y -= gravity * dt
这里有两点需要注意。首先,语句for i in ti.grouped(x)意味着将循环迭代x的所有元素,而不管x中有多少维度。其次,也是最重要的是:注解@ti.kernel意味着Taichi将自动并行运行函数中的任何顶级for循环。在本例中,Taichi将并行更新v中每个N*N向量的y分量。
接下来,我们来处理弦线的内力计算问题。首先,请注意前面图形中的每个质点最多连接到八个邻接质点。这些连接在我们的程序中表示如下:
links = [[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [1, -1], [-1, 1], [1, 1] links = [ti.Vector(v) for v in links]
从物理角度来看,系统中的每个弹簧s都用固定长度l(s,0)初始化。在任何时间t,如果s的当前长度l(s,t)超过l(s,0),则弹簧将在其端点上施加力,将它们拉在一起。相反,如果l(s,t)小于l(s,0),则弹簧会将端点彼此推开。这些力的大小始终与l(s,0)-l(s,0)的绝对值成正比。此交互由以下代码段捕获:
for i in ti.grouped(x): force = ti.Vector([0.0,0.0,0.0]) for d in ti.static(links): j = min(max(i + d, 0), [N-1,N-1]) relative_pos = x[j] - x[i] current_length = relative_pos.norm() original_length = cell_size * float(i-j).norm() if original_length != 0: force +=stiffness * relative_pos.normalized() * (current_length - original_length) / original_length v[i] +=force * dt
请注意,这个for循环仍应作为substep函数中的顶级for循环,该函数用@ti.kernel注解。这样可以确保并行计算施加到每个质点的弹簧力。stiffness在此是一个常数,用于控制弹簧长度变化的程度。在上述程序中,我们使用stiffness =1600指定它的值。在现实世界中,当弹簧振动时,弹簧中储存的能量会消散到周围环境中,其振动最终停止。为了捕捉这种效应,在每个时间步,我们稍微降低每个点的速度大小:
for i in ti.grouped(x): v[i] *= ti.exp(-damping * dt)
在此,damping取固定值2。
我们还需要处理布料和红球之间的碰撞。要做到这一点,我们只需将质点与球接触时的速度降低到0。这样可以确保布料“挂”在球上,而不是穿透球或向下滑动:
if (x[i]-ball_center[0]).norm() <= ball_radius: v[i] = ti.Vector([0.0, 0.0, 0.0])
最后,我们用每个质点的速度更新其自身的位置:
x[i] += dt * v[i]
这就是我们对一块质量弹簧布料进行并行模拟所需的全部代码。
我们将使用Taichi内置的基于GPU的GUI系统(昵称是“GGUI”)渲染布料。GGUI使用Vulkan图形API进行渲染,因此请确保您的计算机上安装了Vulkan(https://docs.taichi.graphics/lang/articles/misc/ggui)。GGUI支持渲染两种类型的3D对象:三角形网格和粒子。在我们的示例中,将把布料渲染为三角形网格,把红色球渲染为单个粒子。
GGUI表示一个三角形网格,包含两个Taichi场:一个顶点(vertices)场和一个索引(indices)场。顶点场是一个一维场,其中每个元素提取是一个表示顶点位置的三维向量,可能由多个三角形共享。在我们的应用程序中,每个点质量都是一个三角形顶点,因此我们可以简单地将数据从x复制到vertices:
vertices = ti.Vector.field(3, float, N * N) @ti.kernel def set_vertices(): for i, j in ti.ndrange(N, N): vertices[i * N + j] = x[i, j]
请注意,每一帧都需要调用set_vertices,因为顶点位置不断被模拟更新。
我们的布料是用一个质点的N×N网格表示,也可以被看作一个由(N-1)×(N-1)小正方形组成的网格。每个正方形都将渲染为两个三角形。因此,总共有(N-1)×(N-1)×2个三角形。每个三角形将在顶点场中表示为3个整数,该场记录顶点场中三角形顶点的索引。以下代码片段捕获了这一结构:
num_triangles = (N - 1) * (N - 1) * 2 indices = ti.field(int, num_triangles * 3) @ti.kernel def set_indices(): for i, j in ti.ndrange(N, N): if i < N - 1 and j < N - 1: square_id = (i * (N - 1)) + j #正方形的第一个小三角形 indices[square_id * 6 + 0] = i * N + j indices[square_id * 6 + 1] = (i + 1) * N + j indices[square_id * 6 + 2] = i * N + (j + 1) #正方形的第二个小三角形 indices[square_id * 6 + 3] = (i + 1) * N + j + 1 indices[square_id * 6 + 4] = i * N + (j + 1) indices[square_id * 6 + 5] = (i + 1) * N + j
请注意,与函数set_vertices不同,函数set_indices只需要调用一次。这是因为三角形顶点的索引实际上并没有改变——只是位置在改变。
为了将红球渲染为粒子,我们实际上不需要准备任何数据,我们之前定义的ball_center和ball_radius变量就是GGUI所需要的全部内容。
至此,我们已经介绍完本文示例程序的所有核心函数!下面代码展示了我们如何调用这些函数:
init() set_indices() window = ti.ui.Window("Cloth", (800, 800), vsync=True) canvas = window.get_canvas() scene = ti.ui.Scene() camera = ti.ui.make_camera() while window.running: for i in range(30): step() set_vertices() camera.position(0.5, -0.5, 2) camera.lookat(0.5, -0.5, 0) scene.set_camera(camera) scene.point_light(pos=(0.5, 1, 2), color=(1, 1, 1)) scene.mesh(vertices, indices=indices, color=(0.5, 0.5, 0.5), two_sided = True) scene.particles(ball_center, radius=ball_radius, color=(0.5, 0, 0)) canvas.scene(scene) window.show()
需要注意的一个小细节是,我们将在主程序循环中的每一帧调用函数step()30次,而不是调用一次。这样做的目的就是让动画不会运行得太慢。把上述所有代码放在一起,整个程序应该是这样的:
import taichi as ti ti.init(arch=ti.cuda) # 另一种可选择方案: ti.init(arch=ti.cpu) N = 128 cell_size = 1.0 / N gravity = 0.5 stiffness = 1600 damping = 2 dt = 5e-4 ball_radius = 0.2 ball_center = ti.Vector.field(3, float, (1,)) x = ti.Vector.field(3, float, (N, N)) v = ti.Vector.field(3, float, (N, N)) num_triangles = (N - 1) * (N - 1) * 2 indices = ti.field(int, num_triangles * 3) vertices = ti.Vector.field(3, float, N * N) def init_scene(): for i, j in ti.ndrange(N, N): x[i, j] = ti.Vector([i * cell_size , j * cell_size / ti.sqrt(2), (N - j) * cell_size / ti.sqrt(2)]) ball_center[0] = ti.Vector([0.5, -0.5, -0.0]) @ti.kernel def set_indices(): for i, j in ti.ndrange(N, N): if i < N - 1 and j < N - 1: square_id = (i * (N - 1)) + j # 1st triangle of the square indices[square_id * 6 + 0] = i * N + j indices[square_id * 6 + 1] = (i + 1) * N + j indices[square_id * 6 + 2] = i * N + (j + 1) # 2nd triangle of the square indices[square_id * 6 + 3] = (i + 1) * N + j + 1 indices[square_id * 6 + 4] = i * N + (j + 1) indices[square_id * 6 + 5] = (i + 1) * N + j links = [[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [1, -1], [-1, 1], [1, 1]] links = [ti.Vector(v) for v in links] @ti.kernel def step(): for i in ti.grouped(x): v[i].y -= gravity * dt for i in ti.grouped(x): force = ti.Vector([0.0,0.0,0.0]) for d in ti.static(links): j = min(max(i + d, 0), [N-1,N-1]) relative_pos = x[j] - x[i] current_length = relative_pos.norm() original_length = cell_size * float(i-j).norm() if original_length != 0: force +=stiffness * relative_pos.normalized() * (current_length - original_length) / original_length v[i] +=force * dt for i in ti.grouped(x): v[i] *= ti.exp(-damping * dt) if (x[i]-ball_center[0]).norm() <= ball_radius: v[i] = ti.Vector([0.0, 0.0, 0.0]) x[i] += dt * v[i] @ti.kernel def set_vertices(): for i, j in ti.ndrange(N, N): vertices[i * N + j] = x[i, j] init_scene() set_indices() window = ti.ui.Window("Cloth", (800, 800), vsync=True) canvas = window.get_canvas() scene = ti.ui.Scene() camera = ti.ui.make_camera() while window.running: for i in range(30): step() set_vertices() camera.position(0.5, -0.5, 2) camera.lookat(0.5, -0.5, 0) scene.set_camera(camera) scene.point_light(pos=(0.5, 1, 2), color=(1, 1, 1)) scene.mesh(vertices, indices=indices, color=(0.5, 0.5, 0.5), two_sided = True) scene.particles(ball_center, radius=ball_radius, color=(0.5, 0, 0)) canvas.scene(scene) window.show()
注意到,上述代码总行数仅有91行!
我希望你喜欢本文中提供的上述示例程序!如果的确如此,下面几个不同挑战等级的任务留给你:
最後に、Taichi によって上記の 91 行の Python コードで実装できるようになった内容を確認してみましょう。10,000 個の質点と約 100,000 個のバネを備えた質量バネ システムです。
Github ページ
にアクセスしてください。詳細なドキュメントと Taichi プロジェクトの多くの例が見つかり、どれも興味深いものです。最後に、並列コンピューティング用のフレンドリーで強力な言語を開発するという使命を信じているのであれば、オープンソースのコントリビューターとして Taichi に参加することを大歓迎です。 翻訳者紹介Zhu Xianzhong 氏、51CTO コミュニティー編集者、51CTO エキスパートブロガー、講師、濰坊の大学のコンピューター教師、そして大学のベテランフリーランスのプログラミング業界。初期の頃は、さまざまな Microsoft テクノロジに焦点を当てていました (ASP.NET AJX および Cocos 2d-X に関連する 3 冊の技術書籍を編集しました)。過去 10 年間は、オープンソースの世界に専念してきました (人気のある完全なソースに精通しています)。スタックWeb開発技術)を学び、OneNet/AliOS Arduino/ESP32/Raspberry PiなどのIoT開発技術やScala Hadoop Spark Flinkなどのビッグデータ開発技術について学びました。 原題: A Beginner's Guide to High-Performance Computing in Python、著者: Dunfan Lu以上がTaichi をベースにした Python によるハイパフォーマンス コンピューティングの入門ガイドの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。