Skip to content

Commit fd40975

Browse files
committed
n-body: elaborate on several possible strategies
1 parent 0713aba commit fd40975

File tree

11 files changed

+1466
-26
lines changed

11 files changed

+1466
-26
lines changed

notebooks/numpy-tps/n-body/.teacher/README-n-body-corrige-nb.md

Lines changed: 173 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
---
2-
authors:
3-
- name: Aubin Geoffre
4-
- name: Thierry Parmentelat
52
jupytext:
63
formats: md:myst
74
text_representation:
@@ -25,7 +22,7 @@ pour faire cette activité sur votre ordi localement, {download}`commencez par t
2522

2623
dans ce TP on vous invite à écrire un simulateur de la trajectoire de n corps qui interagissent entre eux au travers de leurs masses, pour produire des sorties de ce genre
2724

28-
```{image} media/init3.png
25+
```{image} media/init3-1.png
2926
:align: center
3027
:width: 600px
3128
```
@@ -372,13 +369,14 @@ masses, positions, speeds = init3()
372369
simulation3 = simulate(masses, positions, speeds, nb_steps=steps)
373370
draw(simulation3, masses, colors3);
374371
375-
plt.savefig("init3.png")
376-
np.savetxt("simulation3.txt", simulation3.reshape(-1))
372+
plt.savefig("init3-1.png")
373+
np.savetxt("init3-simu-1.txt", simulation3.reshape(-1))
377374
print(simulation3.shape)
378375
```
379376

380-
et avec ces données vous devriez obtenir plus ou moins une sortie de ce genre
381-
```{image} media/init3.png
377+
et avec ces données vous devriez obtenir plus ou moins une sortie de ce genre
378+
mais [voyez aussi la discussion ci-dessous sur les diverses stratégies possibles](label-n-body-strategies)
379+
```{image} media/init3-1.png
382380
```
383381

384382
+++
@@ -466,13 +464,174 @@ def animate(simulation, masses, colors=None, scale=5., interval=50):
466464
scat.set_offsets(np.c_[x, y])
467465
return scat
468466
469-
ani = FuncAnimation(fig, update, frames=nb_steps,
470-
init_func=init, blit=True, interval=interval)
467+
animation = FuncAnimation(
468+
fig, update, frames=nb_steps,
469+
init_func=init, blit=True, interval=interval
470+
)
471471
plt.close()
472-
return ani
472+
return animation
473473
474+
def animate_from_file(filename):
475+
simulation = np.loadtxt(filename).reshape((100, 2, 3))
476+
animation = animate(simulation, masses, colors=colors3)
477+
return HTML(animation.to_jshtml())
474478
475-
simulation3 = np.loadtxt("data/simulation3.txt").reshape((100, 2, 3))
476-
animation = animate(simulation3, masses, colors=colors3)
477-
HTML(animation.to_jshtml())
479+
animate_from_file("data/init3-simu-1.txt")
478480
```
481+
482+
(label-n-body-strategies)=
483+
## plusieurs stratégies
484+
485+
Pour les curieux, vous avez sans doute observé qu'il y a plusieurs façons possibles d'écrire la fonction `simulate()`
486+
487+
dans ce qui suit, on note l'accélération $a$, la vitesse $s$ et la position $p$
488+
489+
````{admonition} Approche 1
490+
:class: note
491+
dans l'implémentation qui a servi à calculer l'illustration ci-dessus, on a écrit principalement ceci:
492+
- on calcule l'accélération
493+
- ce qui permet d'extrapoler les vitesses
494+
$s = s + a.dt$
495+
- et ensuite d'extrapoler les positions
496+
$p = p + s.dt$
497+
```{admonition} 1bis
498+
:class: tip
499+
une variante consiste à intervertir les deux, en arguant du fait que la vitesse à l'instant $t$ agit sur la position à l'instant $t$
500+
- on extrapole d'abord les positions
501+
- puis seulement on calcule l'accélération
502+
- enfin on extrapole les vitesses
503+
```
504+
````
505+
````{admonition} Approche 2
506+
:class: tip
507+
dans cette approche plus fine, on utiliserait deux versions de l'accélération (l'instant présent et l'instant suivant), et un dévelopmment du second ordre, ce qui conduirait à
508+
- calculer la position comme $p = p + s.dt + \frac{a}{2}.dt^2$
509+
- calculer les accélérations $a_+$ sur la base de cette nouvelle position
510+
- estimer l'accélération sur l'intervalle comme la demie-somme entre les deux accélérations
511+
$a_m = (a+a_+)/2$
512+
- mettre à jour les vitesses
513+
$s = s + a_m.dt$
514+
- ranger $a_+$ dans $a$ pour le prochain instant
515+
````
516+
517+
+++
518+
519+
Bref, vous voyez qu'il y a énormément de liberté sur la façon de s'y prendre
520+
Ce qui peut expliquer pourquoi vous n'obtenez pas la même chose que les illustrations avec pourtant les mêmes données initiales
521+
522+
D'autant que, c'est bien connu, ce problème des n-corps est l'exemple le plus célèbre de problème instable, et donc la moindre divergence entre deux méthodes de calcul peut entrainer de très sérieux écarts sur les résultats obtenus
523+
524+
+++
525+
526+
Voici d'ailleurs les résultats obtenus avec ces deux approches alternatives, et vous pouvez constater qu'effectivement les résultats sont tous très différents !
527+
528+
+++
529+
530+
### approche 1bis
531+
532+
```{code-cell} ipython3
533+
# prune-cell
534+
535+
def simulate_1bis(masses, positions, speeds, dt=0.1, nb_steps=100):
536+
# initialize result
537+
N = masses.shape[0]
538+
result = np.empty((nb_steps, 2, N))
539+
# store initial position
540+
result[0] = positions
541+
542+
# because we have stored the initial state
543+
# we need to start at 1 here, we dont have space for more
544+
for step_counter in range(1, nb_steps):
545+
# apply on positions
546+
positions += dt*speeds
547+
548+
# compute forces and accelerations
549+
accelerations = forces(masses, positions) / masses
550+
551+
# apply on speeds
552+
speeds += dt*accelerations
553+
554+
# store current position
555+
result[step_counter] = positions
556+
return result
557+
```
558+
559+
```{code-cell} ipython3
560+
# prune-cell
561+
562+
steps = 100
563+
564+
masses, positions, speeds = init3()
565+
simulation3 = simulate_1bis(masses, positions, speeds, nb_steps=steps)
566+
draw(simulation3, masses, colors3);
567+
568+
plt.savefig("init3-1bis.png")
569+
np.savetxt("init3-simu-1bis.txt", simulation3.reshape(-1))
570+
print(simulation3.shape)
571+
```
572+
573+
````{grid} 2 2 2 2
574+
```{image} media/init3-1bis.png
575+
```
576+
```{code-cell} python
577+
:tags: [remove-input]
578+
animate_from_file("data/init3-simu-1bis.txt")
579+
```
580+
````
581+
582+
+++
583+
584+
### approche 2
585+
586+
```{code-cell} ipython3
587+
# prune-cell
588+
589+
def simulate_2(masses, positions, speeds, dt=0.1, nb_steps=100):
590+
# initialize result
591+
N = masses.shape[0]
592+
result = np.empty((nb_steps, 2, N))
593+
# store initial position
594+
result[0] = positions
595+
596+
# initialize accelerations
597+
accelerations = forces(masses, positions) / masses
598+
599+
# because we have stored the initial state
600+
# we need to start at 1 here, we dont have space for more
601+
for step_counter in range(1, nb_steps):
602+
# compute positions
603+
positions = positions + speeds * dt + accelerations * dt**2 * 0.5
604+
# compute forces and accelerations for next instant
605+
next_accelerations = forces(masses, positions) / masses
606+
# use half-mean as an approximation of acceleration over the period
607+
speeds += 0.5 * (accelerations + next_accelerations)/2 * dt
608+
# save for next iteration
609+
accelerations = next_accelerations
610+
611+
# store current position
612+
result[step_counter] = positions
613+
return result
614+
```
615+
616+
```{code-cell} ipython3
617+
# prune-cell
618+
619+
steps = 100
620+
621+
masses, positions, speeds = init3()
622+
simulation3 = simulate_2(masses, positions, speeds, nb_steps=steps)
623+
draw(simulation3, masses, colors3);
624+
625+
plt.savefig("init3-2.png")
626+
np.savetxt("init3-simu-2.txt", simulation3.reshape(-1))
627+
print(simulation3.shape)
628+
```
629+
630+
````{grid} 2 2 2 2
631+
```{image} media/init3-2.png
632+
```
633+
```{code-cell} python
634+
:tags: [remove-input]
635+
animate_from_file("data/init3-simu-2.txt")
636+
```
637+
````
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
README*
2+
data/
3+
media/
95.6 KB
Binary file not shown.

notebooks/numpy-tps/n-body/README-n-body-nb.md

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
---
2-
authors:
3-
- name: Aubin Geoffre
4-
- name: Thierry Parmentelat
52
jupytext:
63
formats: md:myst
74
text_representation:
@@ -25,7 +22,7 @@ pour faire cette activité sur votre ordi localement, {download}`commencez par t
2522

2623
dans ce TP on vous invite à écrire un simulateur de la trajectoire de n corps qui interagissent entre eux au travers de leurs masses, pour produire des sorties de ce genre
2724

28-
```{image} media/init3.png
25+
```{image} media/init3-1.png
2926
:align: center
3027
:width: 600px
3128
```
@@ -258,8 +255,9 @@ pour commencer et tester, on se met dans l'état initial reproductible
258255
# draw(simulate(masses, positions, speeds), masses, colors3)
259256
```
260257

261-
et avec ces données vous devriez obtenir plus ou moins une sortie de ce genre
262-
```{image} media/init3.png
258+
et avec ces données vous devriez obtenir plus ou moins une sortie de ce genre
259+
mais [voyez aussi la discussion ci-dessous sur les diverses stratégies possibles](label-n-body-strategies)
260+
```{image} media/init3-1.png
263261
```
264262

265263
+++
@@ -347,13 +345,93 @@ def animate(simulation, masses, colors=None, scale=5., interval=50):
347345
scat.set_offsets(np.c_[x, y])
348346
return scat
349347
350-
ani = FuncAnimation(fig, update, frames=nb_steps,
351-
init_func=init, blit=True, interval=interval)
348+
animation = FuncAnimation(
349+
fig, update, frames=nb_steps,
350+
init_func=init, blit=True, interval=interval
351+
)
352352
plt.close()
353-
return ani
353+
return animation
354354
355+
def animate_from_file(filename):
356+
simulation = np.loadtxt(filename).reshape((100, 2, 3))
357+
animation = animate(simulation, masses, colors=colors3)
358+
return HTML(animation.to_jshtml())
355359
356-
simulation3 = np.loadtxt("data/simulation3.txt").reshape((100, 2, 3))
357-
animation = animate(simulation3, masses, colors=colors3)
358-
HTML(animation.to_jshtml())
360+
animate_from_file("data/init3-simu-1.txt")
359361
```
362+
363+
(label-n-body-strategies)=
364+
## plusieurs stratégies
365+
366+
Pour les curieux, vous avez sans doute observé qu'il y a plusieurs façons possibles d'écrire la fonction `simulate()`
367+
368+
dans ce qui suit, on note l'accélération $a$, la vitesse $s$ et la position $p$
369+
370+
````{admonition} Approche 1
371+
:class: note
372+
dans l'implémentation qui a servi à calculer l'illustration ci-dessus, on a écrit principalement ceci:
373+
- on calcule l'accélération
374+
- ce qui permet d'extrapoler les vitesses
375+
$s = s + a.dt$
376+
- et ensuite d'extrapoler les positions
377+
$p = p + s.dt$
378+
```{admonition} 1bis
379+
:class: tip
380+
une variante consiste à intervertir les deux, en arguant du fait que la vitesse à l'instant $t$ agit sur la position à l'instant $t$
381+
- on extrapole d'abord les positions
382+
- puis seulement on calcule l'accélération
383+
- enfin on extrapole les vitesses
384+
```
385+
````
386+
````{admonition} Approche 2
387+
:class: tip
388+
dans cette approche plus fine, on utiliserait deux versions de l'accélération (l'instant présent et l'instant suivant), et un dévelopmment du second ordre, ce qui conduirait à
389+
- calculer la position comme $p = p + s.dt + \frac{a}{2}.dt^2$
390+
- calculer les accélérations $a_+$ sur la base de cette nouvelle position
391+
- estimer l'accélération sur l'intervalle comme la demie-somme entre les deux accélérations
392+
$a_m = (a+a_+)/2$
393+
- mettre à jour les vitesses
394+
$s = s + a_m.dt$
395+
- ranger $a_+$ dans $a$ pour le prochain instant
396+
````
397+
398+
+++
399+
400+
Bref, vous voyez qu'il y a énormément de liberté sur la façon de s'y prendre
401+
Ce qui peut expliquer pourquoi vous n'obtenez pas la même chose que les illustrations avec pourtant les mêmes données initiales
402+
403+
D'autant que, c'est bien connu, ce problème des n-corps est l'exemple le plus célèbre de problème instable, et donc la moindre divergence entre deux méthodes de calcul peut entrainer de très sérieux écarts sur les résultats obtenus
404+
405+
+++
406+
407+
Voici d'ailleurs les résultats obtenus avec ces deux approches alternatives, et vous pouvez constater qu'effectivement les résultats sont tous très différents !
408+
409+
+++
410+
411+
### approche 1bis
412+
413+
+++
414+
415+
````{grid} 2 2 2 2
416+
```{image} media/init3-1bis.png
417+
```
418+
```{code-cell} python
419+
:tags: [remove-input]
420+
animate_from_file("data/init3-simu-1bis.txt")
421+
```
422+
````
423+
424+
+++
425+
426+
### approche 2
427+
428+
+++
429+
430+
````{grid} 2 2 2 2
431+
```{image} media/init3-2.png
432+
```
433+
```{code-cell} python
434+
:tags: [remove-input]
435+
animate_from_file("data/init3-simu-2.txt")
436+
```
437+
````
File renamed without changes.

0 commit comments

Comments
 (0)