Hommage à Newton

S'il y a un type, dans l'Histoire des Sciences, qui mérite le respect absolu, c'est bien Isaac Newton. D'ailleurs, ce n'est pas pour rien si Gotlib en a fait son égérie : les génies se reconnaissent entre eux.

Et pourquoi, s'il vous plaît, faut-il vouer à Isaac Newton une admiration sans borne ? Jugez plutôt !

D'abord, il faut bien l'admettre, Sir Isaac a grave le Swag ! Matez un peu le bôgôsse !

Isaac Newton a grave le
swag
Isaac Newton avait le swag… avant que le swag n'existe.

Qui plus est, ne porte-t-il pas fièrement le nom le plus absolument cool de toute l'histoire de l'humanité ?! Isaac Newton. Newton… Isaac… Newton… « My name is Newton ! Isaac Newton ! Je suis venu sur Terre pour manger des biscuits et révolutionner la Science… et je viens de terminer mon dernier cookie ! ».

Mais l'Histoire ne manque pas de poseurs aux jolis minois ayant plongé dans l'oubli aussi vite que la courbe de sondage d'un président de la république française. Si Zazaac est devenu une icône, c'est pour ses publications scientifiques qui ont tout simplement changé notre façon de comprendre le monde. Sans Newton, pas de voyage sur la lune, pas de satellites, pas de GPS ni de Netflix ou de jeux vidéos.

Voici ce qui s'est passé.

Londres, 1687, par une étouffante journée d'été dans un quartier mal famé. Le vent fait voler la poussière orange de la capitale britannique et transporte aux oreilles du pauvre touriste égaré les notes plaintives et prémonitoires d'un harmonica désaccordé. Derrière une innocente façade banalement décrépie se trouve l'établissement où les grands cerveaux de l'époque viennent épancher leur soif de connaissance et de téquila : la Royal Society of London for the Improvement of Natural Knowledge.

À l'intérieur, des perruques poudrées surplombent les crânes d'hommes délabrés éclusant les pires tord-boyaux tout en débattant des équations qui régissent l'allongement des ressorts ou de l'utilité du whisky frelaté pour dératiser un laboratoire de chimie.

Soudain, un fracas ! Des échardes de bois volent dans la pièce tandis que les regards se tournent vers l'entrée. La porte en bois grinçant qui s'y trouvait encore quelques secondes auparavant vient d'être propulsée contre le mur avec violence par une botte en cuir usé. En haut de cette botte, une jambe, et au bout de cette jambe, un homme. L'homme… C'est lui. Isaac Newton !

D'un regard panoramique, il balaie la pièce et ses occupants dans le silence le plus total ; on entendrait une mouche résoudre une équation du second degré. Nonchalamment, il jette son mégot, l'écrase dans la poussière, et s'avance vers le comptoir. Le tintement lent et régulier de ses éperons tient l'assistance en haleine. Arrivé au bar, il se retourne et d'un geste vif rejette son poncho par dessus son épaule. D'une besace en cuir jauni, il extrait un lourd manuscrit qu'il projette sur le bois tâché avec le fracas d'un coup de tonnerre, et s'exclame :

« Moi, Isaac Newton, ai découvert que la trajectoire d'un glaviot de tabac chiqué de la bouche au crachoir et celles des mouvements des planètes sont régies par une seule et même loi. J'ai également découvert la formule mathématique qui permet de modéliser cette loi, et qui par conséquent nous permettra de concevoir à terme des engins spatiaux capables de nous emmener sur Mars, même si à vrai dire je ne vois pas trop à quoi ça pourrait bien nous servir. Par cet ouvrage, sobrement intitulé « Principes mathématiques de la philosophie naturelle », je fonde une nouvelle branche de la science que j'intitulerai modestement « Mécanique Newtonienne ». Veuillez maintenant vous aligner afin de patienter pour obtenir l'insigne honneur de pouvoir baiser mon auguste main. »

Quelle classe, ce Newton ! La légende était née !

Les trois lois de Newton

Par cet ouvrage, Newton a donc formulé ses fameuses trois lois, qui permettent de calculer le mouvement des corps (« corps » au sens large) dans l'espace. Et ça sert à quoi ? À tout ! À calculer la trajectoire d'un avion ou d'un boulet de canon, à calibrer la puissance d'un moteur en fonction du poids du véhicule, à calculer la poussée nécessaires pour mettre un satellite en orbite, à prédire l'arrivée des comètes, etc.

Mais quelles sont ces fameuses trois lois ?

La première loi s'énonce ainsi (j'ai volontairement simplifié les formulations dans un but évident de pédagogie) : « Tant qu'on n'y touche pas, un truc qui ne bouge pas continue à ne pas bouger, tandis qu'un truc qui bouge continue à bouger ». Un principe que nous connaissons sous le terme d'« inertie ». Cela nous paraît évident mais sachez qu'à l'époque, c'était une révolution. Car en ce temps, tout le monde pensait que puisque les planètes avançaient dans l'espace, c'est qu'une mystérieuse force s'appliquaient sur elles en permanence. Or, Newton nous dit que c'est faux : si les planètes avancent, c'est parce qu'elles sont déjà en mouvement et qu'aucune force ne les empêche de continuer à le faire.

Sur Terre, il est difficile de tester cette loi expérimentalement. Vous pourriez donner une impulsion à une bille pour la faire rouler sur un sol plat, et constater qu'elle continue à avancer une fois qu'elle a quitté votre main. Néanmoins, la bille fini immanquablement par s'arrêter, parce que d'autres forces s'appliquent sur elle, et notamment le frottement de la bille contre le sol.

Dans l'espace profond, en revanche, il n'y a pas de frottement. Si vous donniez une pichenette à la même bille, elle continuerait d'avancer ad vitam eternam, à condition bien entendu de ne pas rencontrer d'astéroïde ou d'être capturée par la gravité d'un astre.

La deuxième loi s'énonce ainsi : « quand on pousse un truc, il bouge, qui plus est dans la direction de la poussée ; et plus on pousse fort et plus il bouge vite. »

Cette loi est très intuitive : une force appliquée à un truc se traduira par du mouvement. Poussez votre voiture en panne, elle avance ; demandez à vos voisins de vous aider à pousser, elle avancera d'autant plus vite. Et si vous exercez une pression constante, la voiture va de plus en plus vite : elle accélère. La gravité est un autre exemple de force qui nous pousse tous vers le bas et maintient nos pieds au sol.

Cette loi nous paraît tellement évidente qu'elle en devient triviale ; à quoi peut-elle concrètement servir ? Et bien, c'est ce que nous allons voir plus loin.

La troisième loi, quant à elle, s'exprime ainsi : « Si vous poussez sur un truc, le truc, en retour, pousse sur vous avec une intensité égale, mais dans l'autre sens ».

Cette loi nous explique comment un engin spatial peut avancer. En faisant cramer du carburant et en laissant le souffle de l'explosion être éjecté vers le bas, la fusée est poussée vers le haut et s'envole.. Simple, non ?

Ces trois lois ont fondé la branche de la science qui permet d'étudier et de prédire le mouvement des corps dans l'espace : la mécanique newtonienne. Mais Newton ne s'est pas arrêté là, car il a également formulé une autre loi : la loi universelle de la gravitation.

La loi de la gravitation : pourquoi les choses tombent-elles ?

Les trois lois énoncées plus haut permettent de comprendre pourquoi les planètes continuent d'avancer au lieu de s'arrêter. Mais pourquoi tournent elles au lieu de poursuivre en ligne droite pour aller se perdre dans l'espace ?

On connaissait déjà depuis le début du siècle les règles mathématiques permettant de décrire les orbites des planètes et des comètes de manière très précise, grâce aux travaux de nombreux scientifiques ayant culminé avec la publication des lois de Kepler.

Mais Kepler était un bigot. Il pouvait calculer les orbites, mais il ne pouvait pas les expliquer autrement que par la volonté divine. Pour être honnête, Kepler avait de bonnes excuses, notamment le fait qu'à l'époque, les incroyants avaient une fâcheuse tendance à se retrouver pendus à l'arbre du village.

Quoi qu'il en soit, en se basant sur les travaux de Kepler, Newton a publié une loi dite loi de la gravitation. Cette loi, combinée aux trois précédentes, permet d'expliquer tous les mouvements des corps, aussi bien les orbites des planètes que la trajectoire d'un mioche qui trébuche sur un trottoir et se vautre par terre.

Étant données ses implications phénoménales, cette loi est étonnamment simple, et s'énonce ainsi (tenez-vous bien) : « Prenez deux trucs dans l'Univers, absolument n'importe quelle paire de trucs, pourvu qu'ils soient pourvus d'une masse. Peu importe leurs emplacements respectifs, ces deux trucs s'attirent l'un l'autre d'une manière égale ; ils s'attirent d'autant plus que leur masse est importante, d'autant moins que leur distance augmente. » Je vous laisse consulter Wikipédia pour obtenir la formule mathématique.

Pauvre Newton

Grâce à ces publications, Newton est devenu l'un des scientifiques les plus respectés de l'Histoire. Il faudra attendre Einstein pour montrer que ces lois ne sont pas vraiment universelles, mais une simple approximation d'un modèle plus général et ne s'appliquant que dans des cas biens particuliers.

Quoi qu'il en soit, des siècles plus tard, les formules de Newton sont toujours utilisées dans les calcules nécessaires à la mise de satellites sur orbite. Respect.

Jouons avec la gravité

La compréhension des lois de Newton fait qu'il devient très facile de simuler informatiquement des phénomènes physiques. Voici par exemple une simulation de l'orbite des cinq planètes les plus proche du Soleil. Vous aurez reconnu, du centre vers l'extérieur, le Soleil, Mercure, Venus, la Terre, Mars et Jupiter.

Les proportions entre les distances entre les différentes planètes et les périodes de révolutions sont respectées, le reste est fantaisiste pour des questions de rendu.



Joli, non ?

Sur cette simulation, l'on peut constater certains faits amusants à propos des orbites. Notamment le fait que plus une planète est proche du Soleil, plus sa vitesse de révolution est importante. C'est assez logique si l'on y réfléchit quelques minutes : plus on se rapproche de l'étoile, plus la force de gravité augmente ; par conséquent, pour contrebalancer cette force, la planète doit voyager dans l'espace plus rapidement, pour conserver une orbite à peu près circulaire.

Si on pouvait par magie ralentir Mercure, la planète la plus proche du Soleil, pour qu'elle voyage dans l'espace à la même vélocité que Jupiter, le petite planète aurait tôt fait de tomber vers le Soleil avec pour conséquences des effets pyrotechniques sans doutes plutôt spectaculaires. À l'inverse, si l'on bardait Jupiter de réacteur pour la faire accélérer (il faudrait de très gros réacteurs), la planète géante aurait tôt fait de quitter son orbite circulaire pour adopter une orbite beaucoup plus large, voire à échapper à l'influence du Soleil pour partir voyager dans l'espace.

Des orbites en Javascript

Comment implémenter une telle simulation ? Grâce à Newton, c'est une tâche quasi-triviale et nous utiliserons Javascript.

D'abord, il nous un référentiel. Nous utiliserons un simple attribut canvas.

<canvas id="space_canvas" width="800" height="800"></canvas>

Ensuite, nous déclarerons un objet Space qui contiendra l'intégralité de notre API publique.

var canvas = document.getElementById('space_canvas');
var space = new Space(canvas);

// Add the sun
var sunMass = 19891000;
var sunRadius = 15;
var sun = space.addPlanet('Sun', sunMass, sunRadius);
sun.setPosition(400, 400);
sun.color = "rgb(253, 184, 19)";

// Add other planets…
var jupiterMass = 18986;
var jupiterRadius = 8;
var jupiter = space.addPlanet('Jupiter', jupiterMass, jupiterRadius);
jupiter.color = "rgb(201,144,57)";
jupiter.setPosition(789, 400);
jupiter.setVelocity(0, 5);

// …

// Run the game loop
space.start()

Notre objet space est chargé de mettre à jour l'état du monde (les positions des planètes) à intervalles réguliers puis de se dessiner lui-même sur le canvas.

La classe vecteur

Commençons par développer notre brique de base : la classe Vector. Rappelons qu'en mathématiques, un vecteur est un objet à tout faire qui peut être utilisé pour représenter des coordonnées, une direction, une force, une vitesse, une accélération, etc. Deux vecteurs peuvent être additionnés ou soustraits entre eux, et multipliés ou divisés par un nombre réel.

var Vector = function(x, y) {
    this.update(x, y);
};

Vector.prototype.update = function(x, y) {
    this.x = x;
    this.y = y;
};

Vector.prototype.add = function(vec) {
    return new Vector(this.x + vec.x, this.y + vec.y);
};

Vector.prototype.addTo = function(vec) {
    this.x += vec.x;
    this.y += vec.y;
    return this;
};

Vector.prototype.sub = function(vec) {
    return new Vector(this.x - vec.x, this.y - vec.y);
};

Vector.prototype.subFrom = function(vec) {
    this.x -= vec.x;
    this.y -= vec.y;
    return this;
};

Vector.prototype.multiply = function(scalar) {
    return new Vector(this.x * scalar, this.y * scalar);
};

Vector.prototype.multiplyTo = function(scalar) {
    this.x *= scalar;
    this.y *= scalar;
    return this;
};

Vector.prototype.divide = function(scalar) {
    return new Vector(this.x / scalar, this.y / scalar);
};

Vector.prototype.divideBy = function(scalar) {
    this.x /= scalar;
    this.y /= scalar;
    return this;
};

// La norme du vecteur est la longueur du segment qu'il représente
Vector.prototype.norm = function() {
    return Math.sqrt(this.x * this.x + this.y * this.y);
};

// Retourne un vecteur de même dimension mais de norme 1
Vector.prototype.normalized = function() {
    var norm = this.norm();
    return this.divide(norm);

};

Vector.prototype.toString = function() {
    return 'Vector<' + this.x + ',' + this.y + '>';
};

(Cf. le code ayant servi d'inspiration.)

Des planètes

Définissons maintenant un objet planète. D'un point de vue physique, une planète possède une masse, une position dans l'espace et une vélocité. Rappelons que la vélocité désigne à la fois la direction de déplacement et la vitesse. Nous stockons également le rayon de la planète, qui sera utilisé pour l'affichage et le calcul des collisions.

var Planet = function(name, mass, radius) {
    this.name = name;
    this.mass = mass;
    this.radius = radius;
    this.position = new Vector(0, 0);
    this.velocity = new Vector(0, 0);
    this.acceleration = new Vector(0, 0);
    this.color = "rgb(255, 255, 255)";
};

Planet.prototype.setPosition = function(x, y) {
    this.position.update(x, y);
};

Planet.prototype.setVelocity = function(x, y) {
    this.velocity.update(x, y);
};

Planet.prototype.setAcceleration = function(x, y) {
    this.acceleration.update(x, y);
};

// `ctx` est un contexte de canvas
Planet.prototype.draw = function(ctx) {
    ctx.beginPath();
    ctx.fillStyle = this.color;
    ctx.arc(this.position.x, this.position.y, this.radius, 0, 2 * Math.PI);
    ctx.fill();
};

Notez que l'accélération de la planète doit être recalculée à chaque mise à jour de l'état de la simulation et qu'il n'y a donc pas nécessairement besoin de stocker cette valeur. Néanmoins, nous le faisons pour des raisons de performances, et nous verrons pourquoi plus loin.

L'espace

Intéressons nous maintenant à notre classe principale, la classe Space.

var Space = function(canvas) {
    this.canvas = canvas;
    this.width = canvas.width;
    this.height = canvas.height;
    this.ctx = canvas.getContext('2d');
    this.planets = new Array();
    this.G = 0.000492;  // Constante gravitationnelle arbitraire
};

Space.prototype.addPlanet = function(name, mass, radius) {
    var planet = new Planet(name, mass, radius);
    planet.setPosition(0, 0);
    this.planets.push(planet);
    return planet;
};

// Dessine l'espace et toutes les planètes
Space.prototype.draw = function() {
    this.ctx.fillStyle = "rgb(0,24,72)";
    this.ctx.fillRect(0, 0, this.width, this.height);
    this.planets.forEach(function(planet) {
        planet.draw(this.ctx);
    }, this);
};

// Boucle principale qui modélise le temps.
// Tente de viser un rendu de 30 fps, soit environ une frame toutes
// les 30 ms.
Space.prototype.loop = function(timestamp) {
    if (this.lastRender === null) {
        this.lastRender = timestamp;
    }

    var progress = timestamp - this.lastRender;
    if (progress > 30) {
        this.update(progress);
        this.draw();
        this.lastRender = timestamp;
    }

    this.frameId = window.requestAnimationFrame(this.loop.bind(this));
};

// Lance la simulation
Space.prototype.start = function() {
    this.frameId = window.requestAnimationFrame(this.loop.bind(this));
};

// Stoppe la simulation
Space.prototype.stop = function() {
    if (this.frameId !== null) {
        window.cancelAnimationFrame(this.frameId);
        this.frameId = null;
        this.lastRender = null;
    }
};

Je n'expliquerai pas le code de la boucle de rendu en elle-même car ce serait hors-sujet et parce que je ne me suis pas embêté à coder quelque chose qui soit au niveau de l'état de l'art. Vous pouvez en apprendre plus en lisant le code ou en cherchant des articles sur les boucles de jeu en Javascript.

Nous avons maintenant tout le code de mise en place nécessaire. Il nous reste la partie intéressante : l'implémentation des lois de Newton. Ces lois sont utilisées dans la fonction update, appelée plusieurs fois par secondes.

/**
 * Fonction de mise à jour de l'état du monde.
 *
 * @param delta_ms La durée (en ms) depuis le dernier calcul
 * /
Space.prototype.update = function(delta_ms) {

    // Toutes les vitesses sont calculées en pixels par secondes.
    // Or, le delta nous est donné en millisecondes. La première chose à
    // faire est donc de convertir les unités.
    var delta = delta_ms / 1000.0;

    // …
};

Rappelons que la première loi de Newton est la loi d'inertie : un corps en mouvement reste en mouvement. Puisque nos planètes ont une vélocité, elles se déplacent dans l'espace. Il suffit donc, à chaque tour de boucle, d'ajouter la vélocité à la position pour obtenir la nouvelle position.

this.planets.forEach(function(planet) {
    planet.position.addTo(planet.velocity.multiply(delta));
}, this);

Remarquez comme l'implémentation vectorielle et la première loi de Newton s'associent en une implémentation confondante de simplicité et d'élégance !

Désormais nos planètes avancent, c'est une évidence. Néanmoins, elles avancent en ligne droite (ou restent immobiles), parce que la vélocité ne change jamais. Nous allons y remédier en implémentant la seconde loi de Newton.

Rappel sur la seconde loi : quand on applique une force sur un corps, cela se traduit par du mouvement. Il nous suffit donc de calculer les forces qui s'appliquent sur les planètes pour mettre à jour les vélocités.

D'ailleurs, du point de vue de la physique, l'accélération n'est rien d'autre que la modification de la quantité de mouvement. L'implémentation vectorielle nous permet une fois encore une expression limpide :

this.planets.forEach(function(planet) {
    // Calcule l'accéleration de la planète à l'instant T
    var acceleration = this.computeAcceleration(planet);

    // Mise à jour de la vélocité
    planet.velocity.addTo(acceleration.multiplyTo(delta));
}, this);

Est-ce… Est-ce tout ? Et bien oui ! En trois lignes, nous avons codé un simulateur de physique. Néanmoins, nous n'avons pas encore implémenté la méthode qui calcule l'accélération.

Accélère Albert !

Comment calculer l'accélération d'un corps ? C'est très facile, Newton nous explique qu'il suffit de calculer la somme des forces qui s'appliquent sur le corps, et de diviser le tout par sa masse.

Dans notre simulateur, nous ne prenons en compte qu'un seul type de force : la gravité. Néanmoins, il serait très facile d'en rajouter d'autres, pour simuler par exemple un réacteur spatial.

/**
 * Calcule l'accéleration qui s'applique sur la planète.
 *
 */
Space.prototype.computeAcceleration = function(planet) {
    var force = new Vector(0, 0);

    // Toutes les autres planètes exercent une force de gravité sur
    // celle-ci.
    this.planets.forEach(function(otherPlanet) {

        // Pour des raisons de performances, je vais considérer qu'une
        // planète de masse 1 est négligeable par rapport aux autres et
        // m'économiser le calcul.
        if (!Object.is(planet, otherPlanet) && otherPlanet.mass > 1) {

            // La formule de la force gravitationnelle se calcule ainsi :
            // F = (G * m1 * m2) / d^2
            // G : constante gravitationnelle
            // m1 : masse de la première planète
            // m2 : masse de la seconde planète
            // d : distance entre les deux.

            var direction = otherPlanet.position.sub(planet.position);
            var distance = direction.norm();
            var gravity = direction.normalized().multiplyTo(
                this.G * planet.mass * otherPlanet.mass / (distance * distance)
            );
            force.addTo(gravity);
        }
    }, this);

    return force.divideBy(planet.mass);
};

Une nouvelle fois, l'implémentation vectorielle permet une implémentation dont l'élégance confine à la poésie pure. Je vous laisse quelques secondes pour admirer la beauté de ces quelques lignes.

Modélisation du temps, Euler et Verlet

Notre implémentation, quoique parfaitement fonctionnelle et valide sur le plan logique, présente un problème de précision dans le calcul des trajectoires.

Revenons en effet sur la manière dont nous calculons le mouvement des planètes.

var delta = ;

this.planets.forEach(function(planet) {
    planet.position.addTo(planet.velocity.multiply(delta));
    var acceleration = this.computeAcceleration(planet);
    planet.velocity.addTo(acceleration.multiplyTo(delta));
}, this);

En français, ce code s'exprimerait ainsi : à intervalle de temps régulier, nous calculons les forces qui s'appliquent sur la planète. En fonction de ces forces et de la position et de la vélocité de la planète à l'intervalle précédent, nous estimons numériquement la nouvelle position et la nouvelle vélocité. Cette façon de résoudre les équations de mouvement s'appelle la méthode d'Euler.

Ce qu'il est important de comprendre, c'est qu'il s'agit d'une approximation numérique. En effet, nous calculons les forces qui s'appliquent sur la planète toutes les 30 millisecondes (par exemple), mais dans la réalité, ces forces s'appliquent en continu. Sans rentrer dans les détails techniques, on peut comprendre de manière intuitive que chaque calcul introduit une petite erreur, et que ces erreurs s'accumulent jusqu'à donner un grand n'importe quoi. On dit que la méthode d'Euler est instable.

Pour obtenir un calcul exact, il faudrait en fait que notre fonction update soit lancée à chaque fois avec un delta qui tend vers zéro, ce qui est impossible.

Heureusement, il existe des méthodes d'approximations alternatives, qui offrent une meilleure précision. Il en est ainsi de la méthode de Verlet.

Je vous avoue n'avoir pas étudié plus que ça les justifications mathématiques du truc. En revanche, je vous laisse zieuter la nouvelle implémentation de notre méthode update.

Space.prototype.update = function(delta_ms) {
    // Convertissons le delta en secondes
    var delta = delta_ms / 1000.0;

    // Commençons par mettre à jour les positions des planètes
    this.planets.forEach(function(planet) {
        planet.position
            .addTo(planet.velocity.multiply(delta))
            .addTo(planet.acceleration.multiply(delta * delta).divide(2));
    }, this);

    // Une fois *toutes* les nouvelles positions calculées, nous mettons
    // à jour les vélocités.
    // Comme à chaque tour de boucle, nous utilisons l'accélération
    // calculée au tour précédent, cette valeur est stockée en cache
    // pour une question de performance.
    this.planets.forEach(function(planet) {
            var newAcceleration = this.computeAcceleration(planet);
            planet.velocity.addTo(
                planet.acceleration
                    .add(newAcceleration)
                    .multiplyTo(delta)
                    .divideBy(2)
            );
            planet.setAcceleration(newAcceleration.x, newAcceleration.y);
        }
    }, this);
};

Nous pouvons vérifier que cette méthode d'approximation des équations de mouvement donne un résultat qui, du moins à mes yeux, semble relativement plausible.



Pour conclure

J'espère que ces quelques amusants calculs vous auront divertis. À plus pour de nouvelles aventures.