<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://www.meteo-blois.fr/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.meteo-blois.fr/" rel="alternate" type="text/html" hreflang="fr" /><updated>2026-05-14T15:12:39+02:00</updated><id>https://www.meteo-blois.fr/feed.xml</id><title type="html">Météo Blois</title><subtitle>Météo, informatique et autres choses.</subtitle><entry><title type="html">A propos de la condition aux limites</title><link href="https://www.meteo-blois.fr/2018/09/01/partie5.html" rel="alternate" type="text/html" title="A propos de la condition aux limites" /><published>2018-09-01T00:00:00+02:00</published><updated>2018-09-01T00:00:00+02:00</updated><id>https://www.meteo-blois.fr/2018/09/01/partie5</id><content type="html" xml:base="https://www.meteo-blois.fr/2018/09/01/partie5.html"><![CDATA[<p>Dans le tutoriel sur le modèle barotrope, que nous avons implémenté sur une aire limitée, on a discuté de la manière de définir les conditions aux bords du domaine et de coupler avec un modèle global. J’ai expliqué rapidement la technique consistant à créer une zone de transition avec un coefficient alpha décroissant qui permet de faire un rappel  progressif vers les valeurs que l’on souhaite imposer.</p>

<p><img src="/wp-content/uploads/2018/03/zone-de-relaxation.png" alt="Zone de relaxation" /></p>

<p>Au début, j’ai été peu regardant sur la largeur de la zone de transition. En partant sur une décroissance linéaire et une zone de transition de 4 points de grille, j’ai fin par m’apercevoir que c’était peu optimal. Rapidement, on remarque une discontinuité qui apparaît entre la zone de relaxation et le bord de la zone de simulation, qui créé des valeurs de vent et de tourbillon élevées. Sur des simulations assez courtes, cela ne pose pas de problème majeur, mais si l’on prolonge, cela peut compromettre la stabilité ou le réalisme du modèle. Voici le résultat au bout de 72 heures de simulation, on voit bien la ligne de vent fort en bordure de zone :</p>

<p><img src="/wp-content/uploads/2018/08/zone-4-lineaire.png" alt="Zone de relaxation de 4, décroissance linéaire" /></p>

<p>.</p>

<p>Il s’avère que ce problème de conditions aux limites a été étudié [1] : on apprend dans les articles scientifiques que de mauvaises conditions peuvent générer du bruit, en raison du rebond d’ondes sur les bords notamment. Il faut donc faire en sorte que la zone de relaxation se comporte comme un ressort qui amortit le flux qui rentre et sort du domaine (damping), tout en assurant la transition vers les valeurs imposées aux bords. La qualité du damping est impactée par la taille de la zone et par la manière dont le paramètre alpha décroit. Une zone de 6 à 8 mailles est optimale, en dessous c’est pas assez, au-delà ça n’apporte rien. Une décroissance linéaire du paramètre alpha donne des résultats satisfaisants, mais une décroissance non linéaire est bien meilleure, en utilisant une fonction du type arc tangente. Quelle que soit la fonction utilisée, une zone de moins de 6 mailles produit l’effet de discontinuité illustré. Après modification, il suffit de constater la différence avec l’ancienne méthode, la discontinuité a disparu et la transition est plus douce :</p>

<p><img src="wp-content/uploads/2018/09/zone-8-atan.png" alt="Zone de relaxation de 8, fonction de décroissance arc tangente." /></p>

<p>[1] Kallberg, 1977. Test of a lateral boundary relaxation scheme in a barotropic model.</p>

<h1 id="test-de-la-condition-aux-limites">Test de la condition aux limites</h1>

<p>Nous avons déjà parlé de modèles à aire limitée, et précisé <a href="2018/09/a-propos-de-la-condition-aux-limites/">comment spécifier la condition limite latérale du domaine</a>. Je vous propose de refaire un test fait par Kallberg afin d’illustrer les effets induits par une spécification rigide des données à la frontière, et l’intérêt d’une zone de relaxation.</p>

<p>Sur un domaine à aire limitée, qui est en fait un domaine ouvert, il faudrait idéalement laisser entrer et sortir les ondes librement. Une telle condition aux limites est assez complexe à mettre en oeuvre, car elle nécessite d’identifier les flux entrants et sortants pour les traiter différemment. C’est pourquoi il est plus simple de fournir les données aux frontières - on appelle cela la sur-spécification. Cela a malheureusement un effet délétère sur la simulation, en introduisant un bruit important qui grandit avec le temps. Cela limite la durée pratique du calcul sur aire limitée.</p>

<h2 id="le-test">Le test</h2>

<p>Le test fait par Kallberg consiste à créer un domaine avec un géopotentiel constant en tout point, et un vent nul. Sur les bords, ces valeurs restent donc imposées à ces valeurs de départ. Au centre, on créé une perturbation de géopotentiel en forme de bosse. On fait la simulation sans zone de relaxation, plus refaite avec. Le pas de temps est de 10s et la grille fait 40x40 cases de résolution 10km. Les résultats sont présentés ci-dessous, tout d’abord sans zone de relaxation.</p>

<p>Simulation de la perturbation de géopotentiel sans zone de relaxation. Chiffres x1000 mètres.</p>

<p>On voit que l’onde se propage vers l’extérieur, avant d’être réfléchie vers l’intérieur du domaine. Elle ne réussit pas vraiment à s’atténuer, même après 4h. Voyons ce qu’il se passe quand on a une zone de relaxation de 6 cases de large.</p>

<p>Même simulation avec la zone de relaxation.</p>

<p>Cette fois, l’onde se propage puis se retrouve amortie et absorbée sans aucune réflexion. L’onde disparaît quasi -complètement au bout d’un peu plus d’une heure de simulation.</p>

<h2 id="conclusion">Conclusion</h2>

<p>On voit cette donc fois l’intérêt d’une bonne formulation de la condition aux limites pour éviter de polluer la simulation avec un bruit inutile. La stabilité du calcul s’en trouve donc améliorée sur des simulations de plus long terme.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Dans le tutoriel sur le modèle barotrope, que nous avons implémenté sur une aire limitée, on a discuté de la manière de définir les conditions aux bords du domaine et de coupler avec un modèle global. J’ai expliqué rapidement la technique consistant à créer une zone de transition avec un coefficient alpha décroissant qui permet de faire un rappel  progressif vers les valeurs que l’on souhaite imposer.]]></summary></entry><entry><title type="html">Grille C et premiers résultats</title><link href="https://www.meteo-blois.fr/2018/08/01/partie4.html" rel="alternate" type="text/html" title="Grille C et premiers résultats" /><published>2018-08-01T00:00:00+02:00</published><updated>2018-08-01T00:00:00+02:00</updated><id>https://www.meteo-blois.fr/2018/08/01/partie4</id><content type="html" xml:base="https://www.meteo-blois.fr/2018/08/01/partie4.html"><![CDATA[<p>Depuis le tutoriel sur le modèle barotrope, beaucoup de code a été écrit, et de nombreux tests ont été effectués. Un gros travail de refactoring a été effectué pour constituer un framework de développement et test pour expérimenter les variantes du modèle et les futures implémentations comme le modèle barocline. Mettons aujourd’hui le focus sur un perfectionnement du modèle barotrope : l’utilisation de la grille C. Voyons un peu de quoi il s’agit, puis nous discuterons des résultats obtenus.</p>

<h2 id="la-grille-c">La grille C</h2>

<p>Le modèle de notre série de tutoriels est implémenté en grille A, c’est la grille la plus simple : toutes les variables sont représentées sur les mêmes points. Mais le calcul sur cette grille présente un inconvénient, en créant des ondes parasites qu’il est indispensable de filtrer pour exploiter les résultats. Elles sont dûes au découplage des points de grille pairs avec les points de grille impairs, dûs à la discrétisation des dérivées. Les micro-erreurs de calcul finissent par se cumuler et causer deux solutions qui divergent, d’où la création d’ondes de longueur 2*dx. La grille C consiste à décaler la position des variables de vent U et V d’une demi-maille par rapport au géopotentiel. Il faut en tenir compte quand on initialise les données, afin d’interpoler correctement ces variables.</p>

<p><img src="/wp-content/uploads/2018/08/grille-a-et-c.png" alt="Grille A et C" title="Grille A et C" /></p>

<p>Disposition des variables sur la grille A et C</p>

<p>Ceci modifie la manière d’effectuer les calculs, puisque les valeurs des variables ne sont plus toutes définies aux points où l’on souhaite évaluer les dérivées. Il faut alors les reconstituer à partir des moyennes des points autour. Bien que cela fasse un léger surcroît de calcul, la méthode présente quelques avantages. Tout d’abord, l’arithmétique discrète correspondante revient à évaluer certains termes avec un ordre de précision plus grand : les différences centrales sont évaluées avec une distance de DX au lieu de 2*DX en grille A. D’autre part, le décalage des variables a pour effet d’éliminer le découplage à l’origine des ondes parasites. Le modèle en grille C n’a donc pas besoin d’être filtré. Enfin, de part les discrétisations employées, le modèle est bien moins sujet à l’aliasing, autre phénomène de création d’ondes parasites qui peut mener à l’instabilité du calcul. L’absence de filtrage compense largement le petit surcroît de calculs nécessaire pour chaque itération. Mais la méthode est plus contraignante sur le pas de temps, qui doit être réduit de moitié, ce qui est facilement compréhensible compte tenu de la réduction de l’intervalle pour les différences discrètes. Le gain en précision du schéma vaut malgré tout la peine, et il reste parfaitement exploitable.</p>

<h2 id="résultats">Résultats</h2>

<p>A des fins de tests, nous partons d’un scénario réel issu des données GFS du 22/06/2018 à 00h, sur un domaine Atlantique nord-Europe correspondant exactement aux cartes que nous allons tracer. La résolution spatiale est de un degré d’arc en latitude comme en longitude, ce qui fait une maille de l’ordre de 100km. La simulation se fera sur 96h avec un pas de temps de 90s. On historise les résultats toutes les 6h. Les tests seront faits avec le modèle barotrope en grille A non filtré, puis filtré, et enfin le modèle barotrope en grille C. Pour la grille A, on utilise le filtre de Schumann. On comparera les résultats obtenus avec les cartes issues du même run du modèle GFS. Place aux images à t=96h :</p>

<p><img src="/wp-content/uploads/2018/08/epp_prmsl_hgt500_96.png" alt="Modele barotrope en grille A non filtré." title="Modele barotrope en grille A non filtré." /></p>

<p>Modèle barotrope en grille A non filtré, t=96h.</p>

<p><img src="/wp-content/uploads/2018/08/epp_prmsl_hgt500_96-1.png" alt="Modele barotrope en grille A filtré." title="Modele barotrope en grille A filtré." /></p>

<p>Modèle barotrope en grille A filtré, t=96h. Oups…</p>

<p><img src="/wp-content/uploads/2018/08/epp_prmsl_hgt500_96-2.png" alt="Modèle barotrope en grille C" title="Modèle barotrope en grille C" /></p>

<p>Modèle barotrope en grille C, t=96h.</p>

<p>Premiers constats qualitatifs :</p>

<ul>
  <li>Le filtre de Schumann est très destructeur sur les structures. En faisant un lissage assez violent à chaque pas de temps, on se retrouve avec une simulation qui n’a plus rien de réaliste et où tout est plat… Il va donc falloir tester un schéma où le filtre est exécuté moins souvent, par exemple avant d’enregistrer l’historique, une fois toutes les 6h.</li>
  <li>Le modèle en grille A sans filtrage se montre assez bruité comme on peut le voir a l’aspect tremblant des isohypses sur la carte.</li>
  <li>Le modèle en grille C n’a pas de problème de bruit, les champs restent bien lisses.</li>
</ul>

<p>Allez on refait le test en grille A avec un filtrage toutes les 6h :</p>

<p><img src="/wp-content/uploads/2018/08/epp_prmsl_hgt500_96-3.png" alt="Modèle barotrope en grille A filtré toutes les 6h" title="Modèle barotrope en grille A filtré toutes les 6h" /></p>

<p>Modèle barotrope en grille A filtré toutes les 6h, t=96h</p>

<p>C’est mieux ! On constate que le bruitage a disparu, mais par rapport à la grille C il semble y avoir un peu de perte au niveau des structures les plus fines. Le centre de la dorsale anticyclonique est plus lissé, de même que le thalweg au sud-est qui est plus arrondi et moins marqué.  Du côté de la solution trouvée, on constate finalement assez peu de différence entre la grille A et la grille C - du moins si l’on n’abuse pas du filtre de Schumann. Quelques ajustements sont vraisemblablement nécessaires pour éviter que celui-ci n’impacte trop la simulation. Ce qui est intéressant maintenant, c’est de comparer à la réalité. Pour cela, il faudrait récupérer les données des réanalyses du NCEP et tracer les mêmes cartes pour faire ressortir les différences. N’ayant pas ces données sous la main, je me contente de comparer les résultats à la solution qu’a calculé GFS pour ce même run. GFS étant un modèle opérationnel parfaitement validé, la solution calculée reste pour moi suffisamment proche de la réalité à cette échéance. Notez bien que le barotrope ne prévoit que le géopotentiel 500hPa, les isobares de pression au niveau de la mer ne sont donc pas comparables.</p>

<p><img src="/wp-content/uploads/2018/08/gfs_prmsl_hgt500_96.png" alt="La solution obtenue par GFS" title="La solution obtenue par GFS" /></p>

<p>La solution obtenue par GFS, t=96h.</p>

<p>On constate quelques différences. La position de la goutte froide au large de la péninsule ibérique n’est pas exactement au même endroit, ainsi que la dépression à l’est de la Scandinavie. En Méditerranée, le modèle ne creuse pas suffisamment la goutte froide. Sur l’Islande et le Groenland, on voit d’importantes différences au niveau du système dépressionnaire. Nous avons toutefois une solution qui ressemble assez bien à la solution GFS, car les valeurs de géopotentiel pour l’anticyclone sont quand même très proches. On garde bien une structure en oméga, et l’on constate bien une progression d’ouest en est du système dépressionnaire nord-atlantique, bien que celle-ci ne soit pas identique avec GFS. Étant en régime de blocage, les différences sont contenues. En effet, il y a fort à parier que les différences auraient été plus importantes en cas de régime zonal davantage perturbé. N’oublions pas que le modèle barotrope est une simplification à l’extrême des équations de l’atmosphère, qui ne tient pas compte de la température, et qui plus est, sans aucun phénomène physique pris en compte. La simulation est donc complètement inertielle : pas de frottement, pas d’apport solaire, pas d’évaporation… GFS, lui, tient compte de tous ces effets qui modifient l’énergie du système. D’autres différences peuvent être expliquées : au niveau des bords. On le voit bien à l’écrasement de la zone de bas géopotentiel sur le Groenland. Notre modèle étant à aire limitée, il y a une zone de transition qui fait un effet de “rappel” (damping) vers un forçage imposé par le modèle GFS. Nous avons ici choisi de prendre les bords constants tout au long de la simulation, ce qui d’une part dévie de la solution GFS, et d’autre part, finit inévitablement par créer une erreur vers l’intérieur du domaine.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Notre test n’est en aucun cas un vrai test d’évaluation de la qualité de simulation. On se contente de voir si les cartes de géopotentiel ressemblent à la réalité. Une vraie évaluation avec calcul de l’écart quadratique moyen serait bien plus scientifique, mais on remettra ça à plus tard. En tout cas, les tests effectués montrent que le modèle fonctionne et donne un résultat physiquement plausible. C’est très encourageant pour la suite du projet ! Côté temps d’exécution, je n’ai pas noté de gros écarts entre la version en grille A et la grille C, chaque pas de temps prends de l’ordre de 1-2ms sur ma machine avec Firefox. On peut quand même noter qu’au final la grille C prends le double de temps de calcul vu la contrainte supplémentaire sur le pas de temps pour assurer la stabilité numérique. Oh j’oubliais… J’ai finalement baptisé ce projet d’expérimentations sur les modèles du nom de code PIFO (Projet Informatique à Formules Ouvertes). Un petit clin d’œil aux acronymes à la française, qui représente un peu l’état d’esprit du projet. On fait ça pour s’amuser, on verra bien le résultat !</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Depuis le tutoriel sur le modèle barotrope, beaucoup de code a été écrit, et de nombreux tests ont été effectués. Un gros travail de refactoring a été effectué pour constituer un framework de développement et test pour expérimenter les variantes du modèle et les futures implémentations comme le modèle barocline. Mettons aujourd’hui le focus sur un perfectionnement du modèle barotrope : l’utilisation de la grille C. Voyons un peu de quoi il s’agit, puis nous discuterons des résultats obtenus.]]></summary></entry><entry><title type="html">Un modèle météo simplifié en JavaScript – Partie 3 : Finalisation et améliorations</title><link href="https://www.meteo-blois.fr/2018/03/02/partie3.html" rel="alternate" type="text/html" title="Un modèle météo simplifié en JavaScript – Partie 3 : Finalisation et améliorations" /><published>2018-03-02T00:00:00+01:00</published><updated>2018-03-02T00:00:00+01:00</updated><id>https://www.meteo-blois.fr/2018/03/02/partie3</id><content type="html" xml:base="https://www.meteo-blois.fr/2018/03/02/partie3.html"><![CDATA[<p>Voici le dernier article de cette série concernant l’implémentation du modèle météo barotrope, ou modèle en eau peu profonde, en JavaScript. Il nous restait à aborder quelques problématiques et concepts afin d’arriver à une version pleinement fonctionnelle. Nous allons aujourd’hui finaliser l’application.</p>

<h2 id="filtrage">Filtrage</h2>

<p>Si on lance le modèle tel que programmé dans l’épisode précédent, la simulation fonctionne, mais on s’aperçoit rapidement que les champs deviennent très bruités. Et si on laisse tourner la simulation plus longtemps, on voit apparaître des sortes de vagues de 2 mailles de large, voire des zones où le vent devient localement très fort, et qui provoque un creusement ou une élévation brutale du géopotentiel. C’est un phénomène lié au traitement numérique des équations en grille A qui créé des ondes stationnaires. Ceci, cumulé aux défauts du traitement numérique des équations, peut rendre la simulation instable et créer des infinis si on laisse tourner le modèle suffisamment longtemps. De toute façon ceci rend les champs assez inexploitables.</p>

<p><img src="/wp-content/uploads/2018/03/effet-sans-filtrage.png" alt="Simulation sans filtrage" title="Simulation sans filtrage" /></p>

<p>Apparitions d’ondes et d’instabilités locales au bout de 18h de simulation environ.</p>

<p>Il existe une solution pour éliminer ce problème : il faut filtrer les champs. On remarque que ces ondes “parasites” ont une longueur d’onde de deux fois la taille de notre maille - ce qui est d’ailleurs démontrable mathématiquement. Il nous faut donc les supprimer à l’aide d’un filtre qui élimine correctement cette longueur d’onde sans trop altérer le reste. Nous utiliseront un filtre appelé filtre de Shuman qui est très efficace. Il s’agit d’une moyenne pondérée de 3 points de grille, appliquée en deux temps, horizontalement puis verticalement. Ce filtre est à appliquer après chaque itération de l’intégration sur chaque champ, donc dans les fonctions start() et step(). Le plus simple pour comprendre le fonctionnement est de regarder le code du filtre :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.filtreMoyenneX = function(a, v, res)
{
    for (var y=0;y&lt;this.height;y++)
    {
        var i = y\*this.width;
        res\[i\] = a\[i\];
        for(var x=1;x&lt;this.width-1;x++)
        {
            var i = x+y\*this.width;
            res\[i\] = a\[i\]\*(1-v)+(a\[i+1\]+a\[i-1\])\*v/2;
        }
        i = this.width-1+y\*this.width;
        res\[i\] = a\[i\];
    }
}
    
this.filtreMoyenneY = function(a, v, res)
{
    for (var x=0;x&lt;this.width;x++)
    {
        var i = x+this.width\*(this.height-1);
        res\[x\] = a\[x\];
        res\[i\] = a\[i\];
    }
    for (var y=1;y&lt;this.height-1;y++)
    {
        var i = y\*this.width;
        res\[i\] = a\[i\];
        for(var x=1;x&lt;this.width-1;x++)
        {
            var i = x+y\*this.width;
            res\[i\] = a\[i\]\*(1-v)+(a\[i+this.width\]+a\[i-this.width\])\*v/2;
        }
        i = this.width-1+y\*this.width;
        res\[i\] = a\[i\];
    }
}

    
this.filtre = function (a)
{
    var tmp = \[\];
    this.filtreMoyenneX(a, 0.5, tmp);
    this.filtreMoyenneX(tmp, -0.5, a);
    
    this.filtreMoyenneY(a, 0.5, tmp);
    this.filtreMoyenneY(tmp, 0.5, a);
}
</code></pre></div></div>

<p>Le filtrage a l’inconvénient de nécessiter un peu de calculs supplémentaires, mais cela reste gérable. Au niveau du résultat, on constate que cela lisse un peu les champs sans trop altérer les grandes structures. A la résolution où l’on travaille, on voit rapidement que les dorsales ou thalwegs un peu faibles vont se combler plus rapidement. Mais les problèmes d’instabilités locales sont réglés.</p>

<p><img src="/wp-content/uploads/2018/03/effet-avec-filtrage.png" alt="Simulation avec filtrage" title="Simulation avec filtrage" /></p>

<p>Avec filtrage, le problème est résolu mais les champs se trouvent beaucoup plus lissés.</p>

<h2 id="le-problème-des-conditions-aux-limites">Le problème des conditions aux limites</h2>

<p>Nous travaillons sur une aire limitée. Cela signifie qu’aux bord du domaine, nous ne pouvons pas faire évoluer les champs. On peut se contenter de fixer leurs valeurs, et considérer qu’elles n’évoluent pas au cours de la simulation, mais on limite de fait l’échéance de prévision que l’on peut atteindre. Il est souvent plus intéressant de coupler des modèles qui travaillent à aire limitée avec des modèles globaux, comme on fait pour calculer les modèles WRF ou AROME à partir des données de GFS et ARPEGE respectivement. La technique est très simple. On définit une zone de relaxation autour de l’aire de travail, qui fait un certain nombre de mailles de largeur. Cette zone sert de transition entre les données du modèle global et celles de notre modèle. On positionne dessus un coefficient alpha, qui vaut 1 sur le bord, et qui diminue progressivement pour atteindre zéro au centre. A la fin de chaque étape de calcul, on fait un mixage entre les valeurs calculées et les valeurs imposées à l’aide de ce coefficient, la résultante devenant les valeurs de nos champs. Un bout de code et une illustration valent mieux qu’un long discours.</p>

<p><img src="/wp-content/uploads/2018/03/zone-de-relaxation.png" alt="Couplage du domaine avec un modèle extérieur." title="Couplage du domaine avec un modèle extérieur." /></p>

<p>Couplage du domaine avec un modèle extérieur.</p>

<p>Dans la classe Atmosphere, on prévoit la méthode couple() qui fait ce travail de “mixage” :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.couple = function(x, c)
{
    for (var i=0;i&lt;this.height\*this.width;i++)
    {
        x\[i\] = (1-this.alpha\[i\])\*x\[i\] + this.alpha\[i\]\*c\[i\];
    }
}
</code></pre></div></div>

<p>A la fin de chaque étape de calcul on ajoute les appels nécessaires. On utilise des variables dédiées pour stocker les valeurs imposées (suffixées par “_couplage”). Ces valeurs sont fixées par l’utilisateur de la classe.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if (this.relaxation&gt;0)
{
    this.couple(this.U\_t, this.U\_couplage);
    this.couple(this.V\_t, this.V\_couplage);
    this.couple(this.phi\_t, this.phi\_couplage);
}
</code></pre></div></div>

<p>Dans la page du modèle, on aura au préalable chargé des valeurs de couplage pour différentes échéances de temps. Je vous passe les détails, ce qui nous intéresse c’est la manière dont nous fixons les valeurs imposées. C’est le rôle de la fonction coupler(), appelée à chaque itération du modèle. On commence par rechercher dans notre liste d’échéances l’intervalle de temps où l’on se trouve. On boucle, en testant le temps depuis le début de la simulation, jusqu’à trouver notre bonheur, notre liste étant triée. Une fois trouvé l’intervalle, on procède à une interpolation linéaire entre les valeurs fixées aux deux temps correspondants. C’est le rôle de la fonction coupler(). Ensuite, on utilise les fonctions setter pour imposer les valeurs calculées au modèle.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function interpoler(a, b, t1, t2, t, res)
{
    var coef = (t-t1)/(t2-t1);
    for (var i=0;i&lt;atmos.width\*atmos.height;i++)
    {
        res\[i\] = (1-coef)\*a\[i\]+coef\*b\[i\];
    }
}

function coupler()
{
    var t;
    var tprec=Number(valids\[0\])\*3600;
    for (var i=1;i&lt;valids.length;i++)
    {
        t = Number(valids\[i\])\*3600;
        if (atmos.time&gt;=tprec &amp;&amp; atmos.time&lt;t)
        {
            interpoler(h500\[i-1\], h500\[i\], tprec, t, atmos.time, h500\_couplage);
            interpoler(u500\[i-1\], u500\[i\], tprec, t, atmos.time, u500\_couplage);
            interpoler(v500\[i-1\], v500\[i\], tprec, t, atmos.time, v500\_couplage);
            atmos.setPhiCouplage(h500\_couplage);
            atmos.setUCouplage(u500\_couplage);
            atmos.setVCouplage(v500\_couplage);
            return;
        }
        tprec = t;
    }
}
</code></pre></div></div>

<p>Dans la classe Atmosphere, j’ai prévu une variable “relaxation” qui sert à paramétrer la largeur de la zone de relaxation en nombre de points de grille. La valeur doit être fixée avant l’appel à init() pour que l’on puisse initialiser les valeurs de alpha en tout point de la grille. Je vous laisse le soin d’aller voir la section de code correspondant dans la démo, n’étant que de la recette de cuisine il n’est pas intéressant de le reproduire ici. Dans la démo, nous utilisons une zone de relaxation de 4.</p>

<h2 id="réalisme-de-la-simulation">Réalisme de la simulation</h2>

<p>Le but du modèle barotrope est de simuler l’évolution du géopotentiel du sommet de l’atmosphère, limite à partir de laquelle on considère la pression comme nulle. Or, le niveau 500hPa ne correspond pas réellement au sommet, puisqu’il y a encore de la pression au-dessus. Si l’on simule en l’état, on obtient des comportements pas vraiment désirables, comme le fait que les ondes de hauts et bas géopotentiels vont se propager d’est en ouest, ce qui n’est pas réaliste. Il faut donc ramener le problème à un équivalent “eau peu profonde” conforme aux hypothèses sous-jacente à la modélisation. Pour cela, l’auteur Jean Coiffier préconise de simuler non pas directement le géopotentiel Phi=gz du 500Hpa, mais Phi=gz - 40000. Cela revient grossièrement à diminuer la couche simulée de 4000 mètres, on suppose que cette valeur a été choisie soigneusement pour assurer le meilleur ajustement possible. Et on comprends dès lors le nom du modèle : on veut simuler une couche fine. La simulation sera faite sur ce géopotentiel “adapté”, et pour afficher le résultat, il faudra bien entendu refaire le calcul inverse. La fonction setter setPhi() de la classe Atmosphere se charge de faire la conversion préalable. Le vent quand à lui reste inchangé, on utilise bien le vent à 500hPa.</p>

<h2 id="les-invariants-du-modèle">Les invariants du modèle</h2>

<p>Les équations d’un système physique doivent toujours conserver un certain nombre de grandeurs physiques. En général, l’énergie en fait partie. C’est le cas pour le modèle atmosphérique, en plus de trois autres grandeurs : le tourbillon total, la masse, et l’enstropie. Dans la classe Atmosphere, ces invariants sont systématiquement calculés après chaque itération du modèle par la fonction calcDiagnostics(), et ils peuvent être ensuite affichés.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.calcDiagnostics = function()
{
    this.total\_masse = 0;
    this.total\_energie = 0;
    this.total\_tourbillon = 0;
    this.total\_enstropie = 0;
    for(var i=0;i&lt;this.width\*this.height;i++)
    {
        var v = ((this.tourbillon\[i\]+this.f\[i\])/this.phi\[i\]);
        this.total\_masse += this.phi\[i\]\*this.dx\*this.dy/(this.m\[i\]\*this.m\[i\]);
        this.total\_energie += this.phi\[i\]\*(this.phi\[i\]/2+this.K\[i\])\*this.dx\*this.dy/(this.m\[i\]\*this.m\[i\]);
        this.total\_tourbillon += this.phi\[i\]\*v\*this.dx\*this.dy/(this.m\[i\]\*this.m\[i\]);            
        this.total\_enstropie += (this.phi\[i\]/2)\*v\*v\*this.dx\*this.dy/(this.m\[i\]\*this.m\[i\]);
    }
    this.total\_masse \*= this.rho/this.g;
    this.total\_energie \*= this.rho/this.g;
    this.total\_tourbillon \*= this.rho/this.g;
    this.total\_enstropie \*= this.rho/this.g;
}
</code></pre></div></div>

<p>En théorie, ces calculs ne doivent pas changer de valeur quel que soit le moment de la simulation. Dans la pratique, le traitement numérique des équations ne permet pas toujours de les conserver exactement. Selon le schéma d’intégration utilisé, la conservation est plus ou moins heureuse pour certains d’entre eux. Plus le schéma et le type de grille utilisés sont sophistiqués, plus on peut garantir leur préservation, qui est en quelque sorte un indicateur de la qualité de simulation. Dans notre cas, notre schéma étant le plus simple, il n’est pas le mieux loti de ce côté. D’autant que le couplage que nous réalisons vient un peu brouiller les pistes. Vous pourrez toujours vous amuser à surveiller ces valeurs - j’ai prévu ce qu’il faut dans l’interface, mais vous constaterez qu’elles ne sont pas constantes à cause de cela. J’ai toutefois vérifié qu’en l’absence de couplage, les valeurs étaient quasi-constantes ce qui prouve que mon modèle fonctionne au niveau attendu.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Nous voici au terme d’un bon morceau de code ! C’est ce que l’on peut faire de plus basique comme modèle atmosphérique de prévision. Notre implémentation et nos choix de traitement numérique sont les plus simples existants. Que faire maintenant ? On peut tout d’abord essayer d’améliorer le traitement numérique, en utilisant d’autres types de grilles, et/ou  d’autres schémas d’intégration plus sophistiqués. Cela pourrait faire l’objet de futurs articles, je ne sais pas encore si je vais me lancer dans ce projet. C’est intéressant pour apprendre, mais cela sort de l’objectif initial. L’étape suivante, c’est de perfectionner la simulation en prenant en compte les couches d’atmosphère et la température. Pour cela, il faut changer de type de modèle : on parle alors de modèle barocline. C’est un projet qui m’occupe depuis un moment déjà, le modèle est programmé mais le fonctionnement n’est pas stable. J’espère trouver le problème et pouvoir vous en parler prochainement.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Voici le dernier article de cette série concernant l’implémentation du modèle météo barotrope, ou modèle en eau peu profonde, en JavaScript. Il nous restait à aborder quelques problématiques et concepts afin d’arriver à une version pleinement fonctionnelle. Nous allons aujourd’hui finaliser l’application.]]></summary></entry><entry><title type="html">Un modèle météo simplifié en JavaScript – Partie 2 : Le codage</title><link href="https://www.meteo-blois.fr/2018/03/01/partie2.html" rel="alternate" type="text/html" title="Un modèle météo simplifié en JavaScript – Partie 2 : Le codage" /><published>2018-03-01T00:00:00+01:00</published><updated>2018-03-01T00:00:00+01:00</updated><id>https://www.meteo-blois.fr/2018/03/01/partie2</id><content type="html" xml:base="https://www.meteo-blois.fr/2018/03/01/partie2.html"><![CDATA[<p>Nous avons vu dans <a href="/2018/02/01/partie1">la précédente partie la théorie du modèle en eau peu profonde</a>, ainsi que les techniques d’intégration. On va maintenant se lancer dans le code JavaScript.</p>

<h2 id="organisation-du-programme">Organisation du programme</h2>

<p>Notre programme sera constitué de deux grandes parties :</p>

<ul>
  <li>La partie interface utilisateur, qui sera la page web où l’utilisateur pourra faire fonctionner le modèle et visualiser les données,</li>
  <li>La partie modèle proprement dite, sous forme d’une librairie JavaScript qui contiendra le code de simulation.</li>
</ul>

<h3 id="linterface-utilisateur">L’interface utilisateur</h3>

<p>On ne va pas chercher à faire quelque chose de super joli, on veut aller à l’essentiel. Le code sera basé sur la librairie jQuery. Je ne vais détailler que les parties intéressantes du code, le reste n’étant que de la pure programmation HTML/JavaScript. Vous aurez tout loisir d’afficher le code source de la démo pour l’étudier. L’interface utilisateur sera constituée des éléments suivants :</p>

<ul>
  <li>Des boutons Reset, Start et Step. Ils permettront respectivement de remettre le modèle à son état initial (t=0), d’exécuter le premier pas de la simulation en appliquant le schéma d’Euler, et d’exécuter les pas suivants avec le schéma en différences centrales. Comme il s’agit uniquement de tester le modèle, on n’a pas prévu de mode “automatique” où l’exécution se déroule sans avoir à cliquer pour chaque étape.</li>
  <li>Un petit message de statut permettant de savoir où l’on en est.</li>
  <li>Un certain nombre de boutons permettant de choisir la variable du modèle affichée.</li>
  <li>La zone de visualisation. Par défaut, un tableau de chiffres correspondant à la grille du modèle sera affiché sont forme de table HTML. Mais on prévoira une représentation colorée pour le vent et le géopotentiel, plus lisible en terme de représentation. On utilisera là encore des tables HTML pour ne pas se lancer dans des représentations plus complexes. Mais utiliser le canvas HTML5 pour ces représentations pourrait être une amélioration intéressante pour plus tard. Le vent sera représenté par des plages de couleur pour l’intensité, et sa direction approximative par des caractères spéciaux HTML.</li>
</ul>

<p><img src="/wp-content/uploads/2018/03/InterfaceModeleEPP.png" alt="Interface du Modèle " title="Interface du Modèle" /></p>

<p>L’interface du Modèle, affichant le champ de vent</p>

<h3 id="le-modèle">Le modèle</h3>

<p>Nous allons implémenter le modèle sous forme d’une classe JavaScript que nous appellerons Atmosphere, dans un fichier js distinct (mb_meteo_epp.js). Voici l’interface publique de la classe, la description est dans les commentaires :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var Atmosphere = function ()
{
    // \*\*\*\* CONSTANTES TECHNIQUES \*\*\*\*
    // Types de projection cartégienne. m=1 constant
    this.PROJ\_CARTESIEN = 0;
    // Type de projection mercator de diamètre terre. m=cos(lat)
    this.PROJ\_MERCATOR = 1;
    
    // \*\*\*\* PARAMETRES DU MODELE \*\*\*\*
    
    // Type de projection à utiliser pour les équations (détermine m)
    this.projection = this.PROJ\_CARTESIEN;
    
    
    // Pas de grille en degré dans la direction des latitudes.
    this.dlat = 10;
    
    // Pas de grille en degré dans la direction des longitudes.
    this.dlon = 10;
    
    // Pas de grille en X. 1° = 111.11km. Recalculé à partir de dlon.
    this.dx = 1111110;
    
    // Pas de grille en Y. 1° = 111.11km. Recalculé à partir de dlat.
    this.dy = 1111110;

    // Largeur de grille du domaine
    this.width=36;    
    
    // Hauteur de grille du domaine
    this.height=36;

    // Latitude du coin haut gauche du domaine.
    this.nlat = 90;
    
    // Latitude du coin bas droite du domaine.
    this.slat = -80;
    
    // Latitude du coin haut gauche du domaine
    this.wlon = 0;
    
    // Longitude du coin bas droite du domaine
    this.elon = 350;
    
    // Pas de temps (attention à la stabilité !)
    this.dt = 3600;
      
    // Largeur de la zone de relaxation pour le couplage.
    this.relaxation = 2;

    // Combien de temps écoulé depuis le début de simulation
    this.time = 0;

    // \*\*\*\* VARIABLES DE LA SIMULATION \*\*\*\*
    
    // Composantes du vent T
    this.U = \[\];    
    this.V = \[\];
    
    // Geopotentiel pression nulle (=gz où p=0). 
    this.phi = \[\];
    
    // Initialise le géopotentiel 500hPa 
    this.setPhi = function(g500) { }
    
    // Initialise la composante U du vent 500hPa 
    this.setU = function(u500) { }
    
    // Initialise la composante V du vent 500hPa
    this.setV = function(v500) { }
    
    // Initialise le modèle avec les variables qui ont été fournies préalablement.
    this.init = function() { }
    
    // Démarre le modèle en exécutant le schéma d'Euler.
    this.start = function() { }
    
    // Avance le modèle d'un pas en utilisant le schéma explicite centré.
    this.step = function() { }

 }
</code></pre></div></div>

<p>Pour utiliser la classe, on doit paramétrer de la manière suivante, ce qui est fait dans la page HTML :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var atmos = new Atmosphere();
...
$(document).ready(function() {
    atmos.projection = atmos.PROJ\_CARTESIEN;
    atmos.width = 144;
    atmos.height = 72;
    atmos.dt = 360; 
    atmos.dlat = 1;
    atmos.dlon = 1;
    atmos.nlat = 80;
    atmos.slat = atmos.nlat-atmos.height\*atmos.dlon;
    atmos.elon = 40;
    atmos.wlon = atmos.elon-atmos.width\*atmos.dlon;
...
}
</code></pre></div></div>

<p>La définition du domaine mérite sans doutes quelques précisions :</p>

<ul>
  <li>La largeur d’un point de grille en degré d’arc (dlat et dlon)</li>
  <li>La largeur et la hauteur en points de grille (width et height)</li>
  <li>Les latitudes extrêmes nord et sud (nlat et slat)</li>
  <li>Les longitudes extrêmes est et ouest (elon et wlon)</li>
  <li>La projection de grille, que l’on choisit cartésienne, mais le programme supporte aussi une projection Mercator. Souvenez-vous, cela définit le facteur d’échelle m dont on a parlé dans la partie théorique. Le cartésien fixe un m constant à 1 en tout point du domaine.</li>
  <li>Les valeurs choisies permettent d’avoir une simulation stable selon le critère CFL. Le pas de temps est de 360 secondes soit 6 minutes, pour la résolution de 1° d’arc que l’on s’est fixée.</li>
</ul>

<p>Il faut ensuite fournir au modèle les données U, V et Phi grâce aux fonctions setXXX correspondantes. Les données attendues sont sous forme de tableau à une dimension. Les valeurs de chaque ligne de grille sont simplement concaténées l’une à la suite de l’autre, les latitudes les plus au nord venant en premier. Si la grille fait 100 de large, alors les données de la première ligne vont de l’indice 0 à l’indice 99, puis les données de la deuxième ligne vont de l’indice 100 à 199 et ainsi de suite. On a choisi de passer par des “setters” pour paramétrer ces données car nous devrons appliquer quelques transformations aux données au passage, afin de s’adapter au référentiel utilisé et pour le traitement correct du géopotentiel. Rien de très compliqué, mais je ne vais pas vous noyer pour le moment dans ces explications, je me réserve cela pour plus tard. Les données étant renseignées, il ne reste plus qu’à appeler init(), puis start(), et enfin step() autant que nécessaire, ce qui est fait par un peu de jQuery sur le code des liens dont le code est ci-dessous. Notez que j’ai cité la totalité du code de ces gestionnaires d’événements, qui font un certain nombre d’autres choses relatives à l’affichage ou le calcul du temps d’exécution de chaque pas de temps à des fins statistiques. Je reviendrais plus tard sur l’appel à la fonction “coupler()” et son rôle, pour le moment considérons que cette fonction ne fait rien de spécial.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
&lt;script&gt;
...
function reset()
{    
    if (status!="ready") return;
    atmos.setPhi(h500\[0\]);
    atmos.setU(u500\[0\]);
    atmos.setV(v500\[0\]);
    atmos.init();
    
    totalTime = 0;
    totalStep = 0;
    printResult();
}

function start()
{
    if (status!="ready") return;
    var firstTimestamp = new Date().getTime(); 
    coupler();
    atmos.start();
    var secondTimestamp = new Date().getTime();
    lastExecTime = secondTimestamp - firstTimestamp; 
    totalStep++;
    totalTime+=lastExecTime;
}

function step()
{
    if (status!="ready") return;
    var firstTimestamp = new Date().getTime(); 
    coupler();
    atmos.step();
    var secondTimestamp = new Date().getTime();
    lastExecTime = secondTimestamp - firstTimestamp;
    totalStep++;
    totalTime+=lastExecTime;
}
&lt;/script&gt;
...
&lt;a href="#" onclick="reset(); return false;"&gt;\[Reset\]&lt;/a&gt; - 
&lt;a href="#" onclick="start(); printResult(); return false;"&gt;\[Start\]&lt;/a&gt; - 
&lt;a href="#" onclick="step(); printResult(); return false;"&gt;\[Step\]&lt;/a&gt; | 
&lt;span id="statistics"&gt;&lt;/span&gt;
...
</code></pre></div></div>

<h2 id="préparation-et-chargement-des-données">Préparation et chargement des données</h2>

<p>Vu le peu d’exigences en terme de champs de données, n’importe quel modèle accessible en open data peut faire l’affaire pour initialiser notre simulation, on pourrait même partir des réanalyses du NCEP. Pour l’implémentation, j’avais le GFS 0.5° sous la main, c’est donc lui qui me servira. A partir des datasets téléchargés, il suffit d’extraire les champs souhaités au format texte à l’aide de l’utilitaire <a href="http://www.cpc.ncep.noaa.gov/products/wesley/wgrib2/">wgrib2 du NWS</a>. Voici un petit exemple de script Bash permettant d’extraire le vent et le géopotentiel pour nos 24 heures de simulation. Notez que pour initialiser le modèle, seul les champs à t=00 sont requis, mais ici je prends de l’avance pour un peu plus tard, vous verrez pourquoi. On obtient au final un fichier par champ et par échéance.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/sh
for valid in "000" "003" "006" "009" "012" "015" "018" "021" "024"
do
    for hgt in "500"
    do
        wgrib2 gfs.t00z.pgrb2.0p50.f$valid -s | grep HGT:$hgt | wgrib2 -i gfs.t00z.pgrb2.0p50.f$valid -text hgt\_$hgt"\_"$valid.txt
        wgrib2 gfs.t00z.pgrb2.0p50.f$valid -s | grep UGRD:$hgt | wgrib2 -i gfs.t00z.pgrb2.0p50.f$valid -text ugrd\_$hgt"\_"$valid.txt
        wgrib2 gfs.t00z.pgrb2.0p50.f$valid -s | grep VGRD:$hgt | wgrib2 -i gfs.t00z.pgrb2.0p50.f$valid -text vgrd\_$hgt"\_"$valid.txt
    done
done
</code></pre></div></div>

<p>Les fichiers texte sont ensuite chargés en mémoire via la méthode AJAX au chargement de la page, après constitution d’une liste des champs requis. Les tableaux h500, u500 et v500 contiendront une entrées pour les données de chaque échéance.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var h500 = \[\];
var u500 = \[\];
var v500 = \[\];

var valids = \["000", "003", "006", "012", "015", "018", "021", "024"\];
var reslist = \[\];
var status = "loading";
$(document).ready(function() {

....
    for (i=0;i&lt;valids.length;i++) { 
        reslist.push("hgt\_500\_"+valids\[i\]+".txt"); 
        reslist.push("ugrd\_500\_"+valids\[i\]+".txt"); 
        reslist.push("vgrd\_500\_"+valids\[i\]+".txt"); 
    } 
    printLoadingStatus(); 
    $.ajax({ url : "res/run/"+scenario+"/"+reslist\[0\], 
        dataType: "text", 
        success : onFieldDownload }); 
}); 


function onFieldDownload(data) { 
    var f = reslist\[0\].substring(0,1); 
    switch (f) { 
        case "h": h500\[h500.length\]=\[\]; 
            loadField(h500\[h500.length-1\], data); 
            break; 
        case "u": u500\[u500.length\]=\[\]; 
            loadField(u500\[u500.length-1\], data); 
            break; 
        case "v": v500\[v500.length\]=\[\]; 
            loadField(v500\[v500.length-1\], data); 
            break; 
    } 
    reslist.shift(); 
    if (reslist.length&gt;0)
    {
        printLoadingStatus();
        $.ajax({
          url : "res/run/"+scenario+"/"+reslist\[0\],
          dataType: "text",
          success : onFieldDownload
       });
    }
    else
    {
        $(statistics)("Prêt");
        status = "ready";
        reset();
    }
}

function printLoadingStatus()
{
    $(statistics)("Chargement "+reslist\[0\]);
}
</code></pre></div></div>

<p>La fonction loadField() est un peu particulière, et son code est un peu compliqué. En effet, GFS est un modèle global, qui contient plus de données que le domaine que l’on veut utiliser, qui plus est en résolution supérieure. On doit donc prendre une donnée sur deux pour atteindre la résolution de 1°. Pour ne rien simplifier, notre domaine est à cheval sur le méridien de Greenwich, qui correspond aux limites horizontales de la grille de GFS : pour constituer notre grille, on doit aller piocher les données à l’est et à l’ouest du méridien, qui sont à deux endroits différents. Et dernier point, mais là c’est la faute à mes choix techniques, j’ai voulu travailler avec une grille orientée dans le sens d’affichage “informatique”, cad avec les points les plus au nord en haut. Ce qui oblige encore à un parcours inversé dans la grille de GFS. Voici le code et ses calculs d’indices affreux :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function loadField(f, data)
{
    var lines = data.split('\\n');
    var width = 720;
    var height = 361;
    lines.shift();
    
    // Ce qui est à droite de greenwich
    var greenwich = Math.floor(-atmos.wlon/atmos.dlon);
    var ystep = 2\*atmos.dlat;
    var ystart = Math.floor((90-atmos.nlat)\*2);
    var yend = Math.floor((90-atmos.slat)\*2);
    var xstep = 2\*atmos.dlon;
    var xstart = 0;
    var xend = 2\*atmos.elon;
    var i = greenwich;
    for (var y = ystart ; y&lt;yend ; y+=xstep)
    {
        for (var x = xstart ; x&lt;xend ; x+=xstep)
        {
            f\[i\] = Number(lines\[x+(360-y)\*width\]);
            i++;
        }
        i += greenwich;
    }
    
    // Ce qui est à gauche de greenwich
    var xstart = width-Math.floor((-atmos.wlon\*2));
    var xend = 720;
    i = 0;
    for (var y = ystart ; y&lt;yend ; y+=ystep)
    {
        for (var x = xstart ; x&lt;xend ; x+=xstep)
        {
            f\[i\] = Number(lines\[x+(360-y)\*width\]);
            i++;
        }
        i += atmos.width-greenwich;
    }
}
</code></pre></div></div>

<h2 id="le-coeur-du-modèle">Le coeur du modèle</h2>

<p>On va maintenant aborder la partie intéressante : la simulation proprement dite. Si vous avez bien suivi la partie théorique, vous verrez que ça ne présente pas une si grande difficulté.</p>

<h3 id="opérateurs-de-calculs">Opérateurs de calculs</h3>

<p>Commençons par définir quelques opérateurs de calcul dont nous aurons besoin.  Tout d’abord le produit et la somme de deux champs, point à point. Ces deux opérations, qui stockent le résultat dans une variable résultat passée en paramètre, ne présentent pas de grande difficulté :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.product = function(x, y, res)
{
    for(var i=0;i&lt;this.width\*this.height;i++)
    {
        res\[i\] = x\[i\]\*y\[i\];
    }
}

this.sum = function(x, y, res)
{
    for(var i=0;i&lt;this.width\*this.height;i++)
    {
         res\[i\] = x\[i\]+y\[i\];
    }
}
</code></pre></div></div>

<p>Le coeur du modèle repose ensuite sur les calculs de dérivée. Nous aurons besoin de pouvoir calculer des dérivées spatiales selon l’axe x et selon l’axe y. Il s’agit tout simplement des formules que nous avions données dans la partie théorique. Remarquez que ces dérivées ne peuvent être calculées aux frontières du domaine, car sinon les indices seraient en dehors du tableau. Cela signifie que nous ne pourrons pas calculer d’évolution du modèle pour les indices de tableau situés aux limites. Cela posera évidemment des problèmes à mesure que l’on avancera dans la simulation, mais nous verrons comment fixer cela. Voici le code de calcul des dérivées spaciales :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.d\_dx = function(f, res)
{
    for (var y=1;y&lt;this.height-1;y++)
    {
        for(var x=1;x&lt;this.width-1;x++)
        {
            var i = x+y\*this.width;
            res\[i\] = (f\[i+1\]-f\[i-1\])/(2\*this.dx\*this.m\[i\]);
        }
    }
}
    
this.d\_dy = function(f, res)
{
    for (var y=1;y&lt;this.height-1;y++)
    {
        for(var x=1;x&lt;this.width-1;x++)
        {
            var i = x+y\*this.width;
            res\[i\] = (f\[i-this.width\]-f\[i+this.width\])/(2\*this.dy);
        }           
    }
}
</code></pre></div></div>

<h3 id="initialisation">Initialisation</h3>

<p>L’initialisation du modèle sert avant tout à allouer les variables intermédiaires dont nous aurons besoin, et calculer quelques tables de lookup. Javascript ne dispose pas d’instruction permettant d’allouer un tableau, les éléments sont créés au moment de leur première affectation. Pour ne pas avoir de problèmes ou d’erreurs d’exécution, nous devrons initialiser chaque élément de chaque tableau avec une valeur par défaut. Les principales variables à initialiser sont :</p>

<ul>
  <li>On calcule une valeur pour dx et dy en fonction de la résolution en degrés du modèle.</li>
  <li>U_t, V_t, phi_t qui sont les valeurs à T-1 des variables du modèle pour le schéma explicite centré. On les initialise ici aux mêmes valeurs que U, V et phi à t=0, mais c’est plus par convenance que par réelle nécessité - on aurait pu tout aussi bien les mettre à zéro.</li>
  <li>Le paramètre de coriolis f=2Ωsin(lat), qui est une simple table de lookup pour ne pas avoir à recalculer le sinus à chaque fois. Ca fait partie des optimisations faciles.</li>
  <li>Le facteur d’échelle m, qui vaut 1 pour les projections cartésiennes et cos(lat) pour Mercator. On a prévu que m puisse être fourni par l’utilisateur de la classe pour toute autre type de projection ayant une projection cartographique simple.</li>
  <li>On initialise les variables K et tourbillon représentant les termes d’énergie et de tourbillon intervenant dans les équations du modèle. On commence par allouer le tableau, puis on appelle les fonctions permettant de calculer leurs valeurs. Normalement ces termes devraient être calculés au début de chaque itération du modèle. Vu que je souhaite pouvoir débugger les valeurs à chaque étape, il est préférable d’avoir ces valeurs avant de commencer. J’ai avancé leur calcul en fin des fonctions init(), start() et step() pour que la valeur correspondant à l’état actuel du modèle soit disponible à l’affichage immédiatement après le calcul. Au final, ça ne change rien au résultat : c’est juste plus pratique pour moi comme cela.</li>
  <li>On procède également à l’initialisation d’une table de paramètre pour le couplage du modèle. J’en reparlerais ultérieurement.</li>
  <li>Notez l’appel à une fonction de calcul de diagnostics. Ce sont des calculs de vérification de la cohérence du modèle, sur la conservation de certaines valeurs physiques. J’en reparlerais plus tard, ce n’est pas indispensable pour la simulation.</li>
  <li>Le reste des variables initialisées sont des variables temporaires, contenant des produits intermédiaires ou des résultats de calculs utilisés pour certaines opérations.</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.init = function()
{
    var lat = this.nlat\*(Math.PI/180);
    this.dx = 111.1 \* this.dlon \* 1000;
    this.dy = 111.1 \* this.dlat \* 1000;
    this.time = 0;
    for (var y=0;y&lt;this.height;y++)
    {
        for(var x=0;x&lt;this.width;x++)
        {
            var i = x+y\*this.width;
            var h = 0;

            // Paramètre de coriolis et facteur d'échelle en fonction de la latitude
            this.f\[i\] = 2 \* this.omega \* Math.sin(lat);
            switch (this.projection)
            {
                case this.PROJ\_CARTESIEN:
                    this.m\[i\] = 1;
                    break;
                case this.PROJ\_MERCATOR:
                    this.m\[i\] = Math.cos(lat);
                    break;
                default:
                    //supposé fourni par l'appelant
                    //this.m\[i\] = 1;
            }

            // Initialisation du couplage
            if (y==0 || y==this.height-1 || x==0 || x==this.width-1)
            {
                this.alpha\[i\] = 1.0;
            }
            else if (y&lt;1+this.relaxation||y&gt;=this.height-this.relaxation-1
                    ||x&lt;1+this.relaxation||x&gt;=this.width-this.relaxation-1)
            {
                var xd = 0;
                var yd = 0;

                if (x&lt;1+this.relaxation) xd = this.relaxation-x+1;
                else if (x&gt;=this.width-this.relaxation-1) 
                    xd = x-this.width+this.relaxation+2;
                if (y&lt;1+this.relaxation) yd = this.relaxation-y+1;
                else if (y&gt;=this.height-this.relaxation-1) 
                    yd = y-this.height+this.relaxation+2;

                if (xd&lt;yd) xd = yd;
                //else if (xd&lt;yd) xd = yd;

                this.alpha\[i\] = (xd/(this.relaxation+1));
            }
            else 
            {
                this.alpha\[i\] = 0.0;
            }

            // Creation des tableaux
            this.K\[i\] = 0;
            this.tourbillon\[i\] = 0;                
            this.U\_t\[i\] = this.U\[i\];
            this.V\_t\[i\] = this.V\[i\];
            this.phi\_t\[i\] = this.phi\[i\];
            this.dU\_dx\[i\] = 0;
            this.dV\_dy\[i\] = 0;
            this.phi\_U\[i\] = 0;
            this.phi\_V\[i\] = 0;
            this.phi\_K\[i\] = 0;
            this.dx\_phi\_K\[i\] = 0;
            this.dy\_phi\_K\[i\] = 0;
            this.dx\_phi\_U\[i\] = 0;
            this.dy\_phi\_V\[i\] = 0;
        }

        lat -= this.dlat\*(Math.PI/180);
    }

    this.calcEnergy();
    this.calcTourbillon();
    this.calcDiagnostics();        
}
</code></pre></div></div>

<h3 id="calcul-de-lénergie-et-du-tourbillon">Calcul de l’énergie et du tourbillon</h3>

<p>Le calcul de ces termes ne présente pas de difficulté particulière, il suffit d’appliquer les formules que nous avons données en chaque point de grille, en utilisant les opérateurs que nous avons définis précédemment.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.calcEnergy = function()
{
    for(var i=0;i&lt;this.width\*this.height;i++)
    {
        this.K\[i\] = this.m\[i\]\*this.m\[i\]\*(this.U\[i\]\*this.U\[i\]+this.V\[i\]\*this.V\[i\])/2;
    }
}

this.calcTourbillon = function()
{
    this.d\_dy(this.U, this.dU\_dy);
    this.d\_dx(this.V, this.dV\_dx);
    for(var i=0;i&lt;this.width\*this.height;i++)
    {
        this.tourbillon\[i\] = this.m\[i\]\*this.m\[i\]\*(this.dV\_dx\[i\]-this.dU\_dy\[i\]);
    }
}
</code></pre></div></div>

<h3 id="le-schéma-deuler">Le schéma d’Euler</h3>

<p>Le schéma d’Euler, qui sert uniquement au premier pas de temps, n’est pas particulièrement compliqué à implémenter. Il se trouve dans la fonction start(). On commence par calculer des variables intermédiaires correspondant aux dérivées, produits et sommes de champs dont nous aurons besoin pour les calculs. Puis nous calculons trois termes A, B, C correspondant aux dérivées temporelles du vent et du géopotentiel, en suivant les formules données par les équations du modèle. Il suffit ensuite d’appliquer la formule de calcul du schéma pour obtenir les nouvelles valeurs de U, V et phi. Le reste consiste à échanger la valeur actuelle avec la nouvelle valeur,  pour le schéma explicite centré. Et on calcule les nouvelles valeurs de l’énergie, du tourbillon et des diagnostics.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.start = function()
{              
    // Formules intermédiaires et dérivées 
    this.product(this.U, this.phi, this.phi\_U);
    this.d\_dx(this.phi\_U, this.dx\_phi\_U);
    this.product(this.V, this.phi, this.phi\_V);
    this.d\_dy(this.phi\_V, this.dy\_phi\_V);

    this.sum(this.K, this.phi, this.phi\_K);
    this.d\_dx(this.phi\_K, this.dx\_phi\_K);
    this.d\_dy(this.phi\_K, this.dy\_phi\_K);

    // Schema d'Euler pour le premier pas de temps
    for (var y=1;y&lt;this.height-1;y++)
    {
        for(var x=1;x&lt;this.width-1;x++)
        {
            var i = x+y\*this.width;
            var A = (this.tourbillon\[i\] + this.f\[i\])\*this.V\[i\]-this.dx\_phi\_K\[i\];
            var B = -(this.tourbillon\[i\] + this.f\[i\])\*this.U\[i\]-this.dy\_phi\_K\[i\];
            var C = -this.m\[i\]\*this.m\[i\]\*(this.dx\_phi\_U\[i\]+this.dy\_phi\_V\[i\]);

            this.U\_t\[i\] = this.U\[i\] + this.dt\*A;
            this.V\_t\[i\] = this.V\[i\] + this.dt\*B;
            this.phi\_t\[i\] = this.phi\[i\] + this.dt\*C;
        }
    }

...

    var tmp = this.U\_t; this.U\_t = this.U; this.U = tmp;
    tmp = this.V\_t; this.V\_t = this.V; this.V = tmp;
    tmp = this.phi\_t; this.phi\_t = this.phi; this.phi = tmp;

    this.calcEnergy();
    this.calcTourbillon();
    this.calcDiagnostics();

    this.time += this.dt;
}
</code></pre></div></div>

<h3 id="le-schéma-explicite-centré">Le schéma explicite centré</h3>

<p>L’avance par le schéma saute-mouton n’est pas plus compliquée que le schéma d’Euler, en fait le code est tellement similaire que seule change la formule de calcul. Pour ne pas surcharger cet article, je ne répéterais donc pas les parties identiques. En fait, j’aurais même pu faire une économie d’écriture au niveau du programme en factorisant du code, mais quand j’ai développé le modèle ce n’étais pas ma priorité, et maintenant j’avoue que j’ai la flemme de le faire #honte. Le code est dans la fonction step() :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.step = function()
{
    // Formules intermédiaires et dérivées 
...
    // Schema différences centrales
    for (var y=1;y&lt;this.height-1;y++)
    {
        for(var x=1;x&lt;this.width-1;x++)
        {
            var i = x+y\*this.width;
            var A = (this.tourbillon\[i\] + this.f\[i\])\*this.V\[i\]-this.dx\_phi\_K\[i\]; 
            var B = -(this.tourbillon\[i\] + this.f\[i\])\*this.U\[i\]-this.dy\_phi\_K\[i\];
            var C = -this.m\[i\]\*this.m\[i\]\*(this.dx\_phi\_U\[i\]+this.dy\_phi\_V\[i\]);

            this.U\_t\[i\] = this.U\_t\[i\] + 2\*this.dt\*A;
            this.V\_t\[i\] = this.V\_t\[i\] + 2\*this.dt\*B;
            this.phi\_t\[i\] = this.phi\_t\[i\] + 2\*this.dt\*C;
        }
    }

...
}
</code></pre></div></div>

<h2 id="cest-tout-pour-aujourdhui">C’est tout pour aujourd’hui</h2>

<p>On a fini de passer en revue l’essentiel du code du modèle. A ce stade, on peut déjà faire une simulation. Vous avez vu qu’il reste encore des choses que j’ai omises ici, et qui font partie des techniques de modélisation. Je ne pensais pas faire de troisième partie, mais comme cet article est finalement horriblement long, cela est devenu nécessaire pour aborder ces quelques points. Mais au fait, vous lisez ceci ? Vous avez survécu ! Bravo ! Avouez que j’ai été sympa aussi, je vous ai épargné le récit des longues séances de debug, de mise au point, d’incompréhensions et d’arrachages de cheveux en tous genres quand rien ne marchait. <a href="/2018/03/02/partie3">Vers la partie 3 : démo en ligne et améliorations.</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Nous avons vu dans la précédente partie la théorie du modèle en eau peu profonde, ainsi que les techniques d’intégration. On va maintenant se lancer dans le code JavaScript.]]></summary></entry><entry><title type="html">Un modèle météo simplifié en JavaScript - Partie 1 : La Théorie</title><link href="https://www.meteo-blois.fr/2018/02/01/partie1.html" rel="alternate" type="text/html" title="Un modèle météo simplifié en JavaScript - Partie 1 : La Théorie" /><published>2018-02-01T00:00:00+01:00</published><updated>2018-02-01T00:00:00+01:00</updated><id>https://www.meteo-blois.fr/2018/02/01/partie1</id><content type="html" xml:base="https://www.meteo-blois.fr/2018/02/01/partie1.html"><![CDATA[<p>Je vous propose aujourd’hui de relever un challenge de programmation très intéressant ! Il s’agit ni plus ni moins que de coder un modèle météo. Le but n’est pas de révolutionner la science, mais plutôt de réaliser le modèle météo de prévision le plus simple possible. L’objectif initial du projet était de faire un moteur physiquement réaliste mais léger pour un jeu basé sur la météo. On veut donc se contenter de quelque chose de très basique : résolution grossière, physique basique qui va à l’essentiel : nuages, pluie, rayonnement solaire.  Il faut que ce soit très léger en calculs, pour pouvoir tourner en temps réel. Et le tout doit être programmé en JavaScript, parce que c’est bien plus rapide pour proto-typer et tester sans avoir besoin d’outils de développement et d’une tonne de librairies. Armé de mes plus précieux bouquins de météo, je me suis attelé au challenge. Je vous propose de relater mes avancées au fil de l’eau sur ce blog, en espérant que ça vous intéressera. Je ne sait pas où ça nous mènera, on va y aller étape par étape et on verra bien si on arrive au bout ou pas ! Le but est avant tout d’apprendre.</p>

<h2 id="la-stratégie">La stratégie</h2>

<p>Il n’est clairement pas envisageable de se lancer bille en tête dans le codage. Des modèles, il en existe de beaucoup de sortes : barocline, barotrope, en grille A, B, C ou D, à différences centrales, spectral… Il faut donc faire un choix dans tout cela. Sans surprise, on va commencer notre quête par le niveau 1 - voire même le tuto d’initiation, ce n’est pas la peine de se noyer directement en affrontant le boss de fin ;). Gardons toujours à l’esprit l’objectif initial : avoir un modèle simple à implémenter et pas trop lourd en calculs. En d’autres termes, on cherche moins la précision du résultat que la vitesse et la simplicité de calcul.</p>

<p>Notre premier choix se porte donc sans surprise sur le modèle le plus basique que j’ai pu trouver. Il porte le doux nom de modèle barotrope, ou modèle en eau peu profonde. On va commencer par une première partie un peu théorique où l’on va décortiquer le principe de fonctionnement et d’intégration, et puis on va aborder le codage proprement dit dans une seconde partie.</p>

<h2 id="le-modèle-en-eau-peu-profonde">Le modèle en eau peu profonde</h2>

<p>Ce modèle permet seulement de prévoir le géopotentiel à 500hPa (environ 5500 mètres d’altitude), représentatif de l’atmosphère moyenne. Il part de l’hypothèse simplificatrice que l’atmosphère est une couche mince et homogène, qui s’écoule autour de la terre, un peu comme un cours d’eau - d’où son nom. Pour le faire fonctionner, on doit seulement connaître le vent et l’altitude du niveau du sommet de l’atmosphère, celui-ci agissant comme la surface de la “rivière” qui ondule et se déforme au gré du courant. Comme il n’y a pas de notion de température, de vitesse verticale ni de frottements, c’est comme considérer que l’évolution de l’atmosphère est uniquement inertielle. L’approximation est raisonnable pour une prévision à 24 heures. Au-delà, ce n’est plus suffisamment réaliste. Mais ce n’est pas grave en regard de nos objectifs : commençons par implémenter le modèle en eau peu profonde, pour apprendre les techniques numériques et se faire un peu la main.</p>

<p><img src="/wp-content/uploads/2018/02/modele-barotrope.png" alt="Principe du modèle barotrope" title="Principe du modèle barotrope" /></p>

<p>Principe du modèle barotrope</p>

<p>Commençons par jeter un oeil aux équations du modèle :</p>

<p><img src="/wp-content/uploads/2018/02/barotrope-1.png" alt="Equations du modèle barotrope" /></p>

<p>Equations du modèle barotrope</p>

<p>Celles-ci sont écrites directement sous forme de dérivées, c’est-à-dire qu’elles expriment pour chaque unité de temps t de combien varient les variables du modèle en fonction de l’état actuel du système. Voyons ce que nous avons :</p>

<ul>
  <li>Le vecteur V est la première variable du modèle, qui représente le vent. Il a pour composantes U et V, respectivement le vent zonal et longitudinal. C’est le vent global de l’atmosphère, nous initialiserons notre modèle avec le vent à l’altitude 500hPa supposé représentatif de l’atmosphère moyenne.</li>
  <li>La lettre grecque Φ se prononce “Phi” et représente le géopotentiel du sommet de l’atmosphère, la variable que nous souhaitons prévoir. Le géopotentiel représente la quantité gz, g étant la constante gravitationnelle (9.81m par seconde) et z étant l’atitude en mètre. Φs est le géopotentiel de la surface inférieure. Notez qu’on parle ici de sommet de l’atmosphère… alors que le 500hPa que l’on souhaite prévoir n’est pas le sommet ! On verra quand on codera comment on peut faire pour que ça marche.</li>
  <li>La variable f représente le facteur de coriolis, la déviation du vent par la rotation de la terre. La formule est f=2Ω*sin(lat), lat étant la latitude en degré et oméga la vitesse angulaire de rotation de la terre. La force est maximale aux pôles et minimale à l’équateur.</li>
  <li>La variable ξ (prononcer “xi”) représente le tourbillon et sa formule vaut ξ=m²(du/dx-du/dy). La notion de tourbillon est un peu complexe à expliquer, disons juste que c’est un terme qui intervient dans la rotation des masses d’air. Nb : u et v représentent les composantes du vecteur V et x et y les axes du vent, et la lettre d l’opérateur de dérivée.</li>
  <li>Au passage, le terme m représente le facteur d’échelle. Il permet de tenir compte d’une éventuelle projection cartographique, mais pour simplifier nous prendrons ce terme constant à 1.</li>
  <li>Le vecteur k est le vecteur unitaire représentant la verticale, cad l’axe z pointant donc vers le haut. Cf illustration plus bas.</li>
  <li>Enfin la lettre k non vectorielle (à ne pas confondre avec le vecteur unitaire) représente l’énergie. On la définit par la formule k=m²/2 * (u²+v²).</li>
</ul>

<p>Tous les opérateurs de gradient, divergence et de produit vectoriel peuvent paraître barbares… Je ne souhaite pas alourdir cet article en expliquant à quoi ils servent. Développons et écrivons ces équations dans une forme un peu plus “informatisable”. Au final tout se résume à de bonnes vieilles dérivées comme on a appris à l’école, selon un axe x ou y :</p>

<ul>
  <li>du = (xi + f) * v - dérivée_x(k + Phi)</li>
  <li>dv = -(xi + f) * u + dérivée_y(k + Phi)</li>
  <li>dPhi = -m² * (dérivee_x(Phi * u) + dérivée_y(Phi * v)</li>
</ul>

<h2 id="lintégration-du-modèle">L’intégration du modèle</h2>

<p>Réaliser la simulation n’est alors plus qu’un problème d’itération. L’algorithme est le suivant :</p>

<ol>
  <li>Initialiser les variables u, v et Phi avec des données réelles.</li>
  <li>Pour chaque pas de temps:
    <ol>
      <li>Calculer les variables xi et k selon les formules (f sera initialisée une fois pour toutes à l’initialisation du modèle)</li>
      <li>Calculer les termes du, dy et dPhi selon les équations</li>
      <li>Calculer les nouvelles valeurs de u, v et Phi selon la formule d’Euler et le schéma explicite centré</li>
    </ol>
  </li>
</ol>

<p>Il se pose maintenant plusieurs questions auxquelles nous allons devoir répondre :</p>

<ul>
  <li>La représentation des variables, décrites par les équations à l’échelle du continuum, sous forme informatisable</li>
  <li>Comment on calcule une dérivée spatiale</li>
  <li>Comment calculer l’évolution temporelle selon les schémas que nous avons mentionné.</li>
  <li>Les problématiques du traitement numérique</li>
</ul>

<h3 id="la-discrétisation-des-variables">La discrétisation des variables</h3>

<p>Les variables du modèle ont deux dimensions - on appelle cela un champ. Comme on ne peut pas informatiquement représenter les données de façon continue, on va procéder comme pour tout signal numérisé : on discrétise, ce qui est l’équivalent d’un échantillonnage pour le son par exemple. On va définir une grille à deux dimensions, et en chaque point de grille espacés de dx et dy nous aurons les valeurs de nos variables. C’est tout simplement un tableau. Dans notre cas, toutes les variables sont définies aux mêmes points de grille, on dit qu’il s’agit d’une grille de type A. Il existe des dispositions plus évoluées qui permettent de façon astucieuse d’augmenter la précision des calculs. Mais c’est hors du cadre de cet article, on aura l’occasion d’y revenir.</p>

<p><img src="/wp-content/uploads/2018/02/grille_1_degre_referentiel-1.png" alt="Grille et référentiel utilisés dans les articles" title="Grille et référentiel utilisés dans les articles" /></p>

<p>Grille et référentiel utilisés dans les articles. La grille fait 1 degré d’arc. On représente également les composantes du vent V, et la taille de grille dx et dy. Les variables du modèle sont positionnées aux intersections des traits de grille.</p>

<h3 id="comment-calculer-une-dérivée-spatiale">Comment calculer une dérivée spatiale</h3>

<p>Ce n’est pas un problème très compliqué, on se contentera de donner la formule suivante, qui calcule la dérivée d’une variable A au point X selon l’axe x :</p>

<p><code class="language-plaintext highlighter-rouge">dA(x)/dx = 1/(2*dx) * ( A(x+dx) - A(x-dx) )</code></p>

<p>Il suffit donc de prendre les valeurs aux points de grille à droite et à gauche, et d’en faire la différence. Je vous laisse le soin d’adapter la formule pour l’axe y ;).</p>

<p>Note pour les matheux : ce calcul est le développement de Taylor du premier ordre de la dérivée.</p>

<h3 id="les-schémas-davance-temporelle">Les schémas d’avance temporelle</h3>

<p>Pour calculer les nouvelles valeurs à T+dt de nos variables, le schéma d’avance temporelle le plus simple est le schéma d’Euler :</p>

<p><code class="language-plaintext highlighter-rouge">A(t+dt) = A(t) + dA(t) * dt</code></p>

<p>Il a le mérite de permettre l’avance dans le temps en ne connaissant que les valeurs des variables à l’instant T. Mais ce schéma est d’une précision insuffisante, on dit qu’il est instable.</p>

<p>Pour avoir une intégration temporelle stable, il existe bien d’autres schémas, certains étant plus complexes que d’autres à mettre en oeuvre. Fidèles à notre objectif de simplification, nous prendrons le schéma d’avance explicite centré. En voici la formule :</p>

<p><code class="language-plaintext highlighter-rouge">A(t+dt) = A(t-dt) + dA(t)*2*dt</code></p>

<p>De part le fait qu’il calcule la valeur en t+dt à partir de la valeur en t-dt, sans tenir compte de la valeur en t, on l’appelle aussi schéma en saute-mouton. Plus précis et stable, ce schéma a l’inconvénient de nécessiter de connaître la valeur précédente des variables. Il n’est donc pas utilisable au démarrage de l’intégration. Pour solutionner le problème, on procède de la manière suivante :</p>

<ul>
  <li>Pour chaque variable, on garde trace variables en t et t-dt.</li>
  <li>Pour le premier pas de temps, initialiser les variables “t-dt” avec les mêmes valeurs que les variables d’initialisation, cad en t=0. Utiliser le schéma d’Euler pour calculer le premier pas en t=dt.</li>
  <li>Pour les pas suivants. Calculer leur valeur en t selon le schéma explicite centré, connaissant t-dt. Puis on échange la valeur courante des variables qui deviennent les valeurs “t-dt”. Et ainsi de suite.</li>
</ul>

<h3 id="problématiques-de-lintégration-numérique">Problématiques de l’intégration numérique</h3>

<p>L’intégration numérique ne fonctionne que sous certaines conditions. On ne peut pas prendre n’importe quelle taille de grille dx, dy ou de pas de temps dt. Il a été démontré que pour que le modèle soit stable, il faut que la condition suivante soit réalisée, dite condition CFL du nom des météorologues qui l’ont démontrée :</p>

<p><code class="language-plaintext highlighter-rouge">U*dt/dx &lt; 1</code></p>

<p>Pour faire simple, U représente la vitesse maximale de propagation que l’on souhaite représenter dans le modèle, tous types d’onde compris. Afin d’avoir une modélisation correcte, on fixe U=300m par seconde.</p>

<p>Pour notre projet, on souhaite travailler à une résolution horizontale d’un degré d’arc, soit environ 111km pour dx. Selon la formule, notre pas de temps ne devra pas dépasser 370 secondes soit un peu plus de 6 minutes - qu’on arrondira volontiers à 360 secondes.</p>

<h2 id="limplémentation-du-modèle">L’implémentation du modèle</h2>

<p>On a maintenant tous les éléments théoriques nécessaires pour se lancer dans l’implémentation en JavaScript. Mais cet article est déjà assez long comme cela, je vous donne rendez-vous pour <a href="/2018/03/01/partie2">la partie 2</a> : on abordera le codage et on présentera une démo en ligne du modèle.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Je vous propose aujourd’hui de relever un challenge de programmation très intéressant ! Il s’agit ni plus ni moins que de coder un modèle météo. Le but n’est pas de révolutionner la science, mais plutôt de réaliser le modèle météo de prévision le plus simple possible. L’objectif initial du projet était de faire un moteur physiquement réaliste mais léger pour un jeu basé sur la météo. On veut donc se contenter de quelque chose de très basique : résolution grossière, physique basique qui va à l’essentiel : nuages, pluie, rayonnement solaire.  Il faut que ce soit très léger en calculs, pour pouvoir tourner en temps réel. Et le tout doit être programmé en JavaScript, parce que c’est bien plus rapide pour proto-typer et tester sans avoir besoin d’outils de développement et d’une tonne de librairies. Armé de mes plus précieux bouquins de météo, je me suis attelé au challenge. Je vous propose de relater mes avancées au fil de l’eau sur ce blog, en espérant que ça vous intéressera. Je ne sait pas où ça nous mènera, on va y aller étape par étape et on verra bien si on arrive au bout ou pas ! Le but est avant tout d’apprendre.]]></summary></entry></feed>