DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»Web Dev Zone»Un guide du débutant sur le calcul hautes performances en Python
    Web Dev Zone

    Un guide du débutant sur le calcul hautes performances en Python

    novembre 30, 2021
    Un guide du débutant sur le calcul hautes performances en Python
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    Depuis la naissance du langage de programmation Python, sa philosophie de base a toujours été de maximiser la lisibilité et la simplicité du code. En fait, la portée de la lisibilité et de la simplicité est si profonde dans la racine de Python que, si vous tapez import this dans une console Python, il récitera un petit poème :

    Beau vaut mieux que laid. Explicite vaut mieux qu’implicite. Simple vaut mieux que complexe. Le complexe vaut mieux que compliqué. L’appartement est mieux que niché. Mieux vaut clairsemé que dense. La lisibilité compte…

    Simple vaut mieux que complexe. La lisibilité compte. Sans aucun doute, Python a en effet assez bien réussi à atteindre ces objectifs : c’est de loin le langage le plus convivial à apprendre, et un programme Python moyen est souvent 5 à 10 fois plus court qu’un code C++ équivalent. Malheureusement, il y a un hic : la simplicité de Python se fait au prix de performances réduites. En fait, il n’est presque jamais surprenant qu’un programme Python soit 10 à 100 fois plus lent que son homologue C++. Il apparaît donc qu’il y a un compromis perpétuel entre vitesse et simplicité, et aucun langage de programmation ne possédera jamais les deux.

    Mais ne vous inquiétez pas, tout espoir n’est pas perdu.

    Taichi : le meilleur des deux mondes

    Le langage de programmation Taichi est une tentative d’étendre le langage de programmation Python avec des constructions qui permettent un calcul polyvalent et hautes performances. Il est intégré de manière transparente dans Python, mais peut invoquer chaque once de puissance de calcul dans une machine – le processeur multicœur et, plus important encore, le GPU.

    Nous allons montrer un exemple de programme écrit en utilisant taichi. Le programme utilise le GPU pour exécuter une simulation physique en temps réel d’un morceau de tissu tombant sur une sphère et restitue simultanément le résultat.

    Écrire un simulateur de physique GPU en temps réel est rarement une tâche facile, mais le code source de Taichi derrière ce programme est étonnamment simple. Le reste de cet article vous guidera tout au long de la mise en œuvre, afin que vous puissiez avoir un avant-goût des fonctionnalités fournies par taichi, et à quel point elles sont puissantes et conviviales.

    Avant de commencer, devinez combien de lignes de code ce programme comprend. Vous trouverez la réponse à la fin de l’article.

    Présentation algorithmique

    Notre programme modélisera le morceau de tissu comme un système masse-ressort. Plus précisément, nous représenterons le morceau de tissu comme un N par N grille de masses ponctuelles, où les points adjacents sont reliés par des ressorts. L’image suivante, fournie par Matthew Fisher, illustre cette structure :

    Le mouvement de ce système masse-ressort est affecté par 4 facteurs :

    • La gravité
    • Forces internes des ressorts
    • Amortissement
    • Collision avec la boule rouge au milieu

    Pour la simplicité de ce blog, nous ignorons les auto-collisions du tissu. Notre programme commence à l’heure t = 0. Ensuite, à chaque étape de la simulation, il avance le temps d’une petite constante dt. Le programme estime ce qui arrive au système dans cette petite période de temps en évaluant l’effet de chacun des 4 facteurs ci-dessus, et met à jour la position et la vitesse de chaque point de masse à la fin du pas de temps. Les positions mises à jour des points de masse sont ensuite utilisées pour mettre à jour l’image rendue à l’écran.

    Commencer

    Bien que Taichi soit un langage de programmation à part entière, il existe sous la forme d’un package Python et peut être installé en exécutant simplement pip install taichi.

    Pour commencer à utiliser Taichi dans un programme python, importez-le sous l’alias ti:

    import taichi as ti

    Les performances d’un programme Taichi sont maximisées si votre machine dispose d’un GPU Nvidia compatible CUDA. Si tel est le cas, ajoutez la ligne de code suivante après l’importation : ti.init(arch=ti.cuda)
    Si vous n’avez pas de GPU CUDA, Taichi peut toujours interagir avec votre GPU via d’autres API graphiques, telles que ti.metal, ti.vulkan et ti.opengl. Cependant, la prise en charge de ces API par Taichi n’est pas aussi complète que sa prise en charge CUDA, donc, pour l’instant, utilisez le backend CPU : ti.init(arch=ti.cpu) Et ne vous inquiétez pas, Taichi est ultra rapide même s’il ne s’exécute sur le processeur. Après avoir initialisé Taichi, nous pouvons commencer à déclarer les structures de données utilisées pour décrire le tissu masse-ressort. On ajoute les lignes de code suivantes :

       
    
        
        N = 128
       
       
    
       
       
    
        
        x = ti.Vector.field(3, float, (N, N))
       
       
    
       
       
    
        
        v = ti.Vector.field(3, float, (N, N))
       

    Ces trois lignes déclarent x et v être des tableaux 2D de taille N par N, où chaque élément du tableau est un vecteur tridimensionnel de nombres à virgule flottante. À Taichi, les réseaux sont appelés « champs », et ces deux champs enregistrent respectivement la position et la vitesse des masses ponctuelles. Notez que, si vous avez initialisé Taichi pour qu’il s’exécute sur un GPU CUDA, ces champs/tableaux seront automatiquement stockés dans la mémoire du GPU. En dehors du tissu, nous devons également définir la balle au milieu :

        
        ball_radius = 0.2
       
       
    
       
       
    
        
        ball_center = ti.Vector.field(3, float, (1,))
       

    Ici, le centre de la balle est un champ 1D de taille 1, avec son seul composant étant un vecteur à virgule flottante tridimensionnel. Après avoir déclaré les champs nécessaires, initialisons ces champs avec les données correspondantes à t = 0. Nous souhaitons nous assurer que, pour toute paire de points adjacents sur la même ligne ou colonne, la distance entre eux est égale à cell_size = 1.0 / N. Ceci est assuré par la routine d’initialisation suivante :

        
        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])

    Pas besoin de s’inquiéter de la signification derrière la valeur de chacun x[i,j] — il est uniquement choisi pour que le tissu tombe à un angle de 45 degrés, comme indiqué dans le gif.

    Simulation

    À chaque pas de temps, notre programme simule 4 choses qui affectent le mouvement du tissu : la gravité, les forces internes des ressorts, l’amortissement et la collision avec la balle rouge.

    La gravité est la plus simple à gérer. Voici le code qui le fait :

    
       
    
        
        @ti.kernel
       
       
    
       
       
    
        
        def step():    
       
       
    
       
       
    
        
            for i in ti.grouped(v):        
       
       
    
       
       
    
        
            v[i].y -= gravity * dt
       
       

    Il y a deux choses à noter ici. Premièrement, for i in ti.grouped(x) signifie que la boucle itérera sur tous les éléments de x, quel que soit le nombre de dimensions dans x. Deuxièmement et surtout, l’annotation ti.kernel signifie que taichi parallélisera automatiquement toutes les boucles for de niveau supérieur à l’intérieur de la fonction. Dans ce cas, taichi mettra à jour le y composant de chacun des N*N vecteurs dans v en parallèle.

    Ensuite, nous allons gérer les forces internes des cordes. Tout d’abord, notez à partir de l’illustration précédente que chaque point de masse est connecté à au plus huit voisins. Ces liens sont représentés dans notre programme comme suit :

        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]
       

    D’un point de vue physique, chaque printemps s dans le système est initialisé avec une longueur de repos, l(s,0). À tout moment t, si la longueur actuelle l(s,t) de s dépasse l(s,0), alors le ressort exercera une force sur ses extrémités qui les rapproche. A l’inverse, si l(s,t) est plus petit que l(s,0), le ressort éloignera les extrémités l’une de l’autre. L’amplitude de ces forces est toujours proportionnelle à la valeur absolue de l(s,0)-l(s,0). Cette interaction est capturée par l’extrait de code suivant :

       
        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
       

    Remarquez que ce for la boucle doit toujours être placée en tant que niveau supérieur for boucle dans le substep fonction, qui a été annotée avec @ti.kernel. Cela garantit que les forces de ressort appliquées à chaque point de masse sont calculées en parallèle. La variable stiffness est une constante qui contrôle la mesure dans laquelle les ressorts résistent au changement de leur longueur. Dans notre programme, nous utiliserons stiffness = 1600.

    Dans le monde réel, lorsque les ressorts oscillent, l’énergie stockée dans les ressorts se dissipe dans l’environnement environnant et ses oscillations finissent par s’arrêter. Pour capturer cet effet, à chaque pas de temps, nous réduisons légèrement l’amplitude de la vitesse de chaque point :

    
       
       
       
       

    for i in ti.grouped(x):

    v[i] *= ti.exp(-damping * dt)

    où damping prend la valeur fixe de 2.

    Nous devons également gérer la collision entre le tissu et la balle rouge. Pour ce faire, on diminue simplement la vitesse d’un point de masse à 0 dès qu’il entre en contact avec la balle. Cela garantit que le tissu « accroche » à la balle au lieu de la pénétrer ou de glisser vers le bas :

    
        
                if (x[i]-ball_center[0]).norm() <= ball_radius:            
       
       
    
       
       
    
        
                    v[i] = ti.Vector([0.0, 0.0, 0.0])
       

    Et enfin, nous mettons à jour la position de chaque point de masse en utilisant sa vitesse :

    Et c’est tout! C’est tout le code dont nous avons besoin pour effectuer une simulation parallèle d’une masse-ressort-tissu.

    Le rendu

    Nous utiliserons le système d’interface graphique intégré basé sur GPU de taichi (surnommé « GGUI ») pour rendre le tissu. GGUI utilise l’API graphique Vulkan pour le rendu, alors assurez-vous que Vulkan est installé sur votre machine. GGUI prend en charge le rendu de deux types d’objets 3D : les maillages triangulaires et les particules. Nous allons rendre le tissu sous la forme d’un maillage triangulaire et la boule rouge sous la forme d’une seule particule.

    GGUI représente un maillage triangulaire avec deux champs de taichi : un champ de vertices, et un champ de indices. Les vertices champs est un champ à 1 dimension où chaque extrait d’élément est un vecteur 3D qui représente la position d’un sommet, éventuellement partagé par plusieurs triangles. Dans notre application, chaque masse ponctuelle est un sommet de triangle, nous pouvons donc simplement copier les données de x dans 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]
       

    Remarquerez que set_vertices doit être appelé à chaque image, car le…

    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.