Skip to content

Commit b2ecbcf

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

File tree

11 files changed

+1467
-34
lines changed

11 files changed

+1467
-34
lines changed

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

Lines changed: 175 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
---
2-
authors:
3-
- name: Aubin Geoffre
4-
- name: Thierry Parmentelat
52
jupytext:
63
formats: md:myst
74
text_representation:
85
extension: .md
96
format_name: myst
7+
format_version: 0.13
8+
jupytext_version: 1.17.3
109
kernelspec:
1110
name: python3
1211
display_name: Python 3 (ipykernel)
1312
language: python
14-
language_info:
15-
name: python
16-
pygments_lexer: ipython3
17-
nbconvert_exporter: python
1813
---
1914

2015
# n-body problem
@@ -25,7 +20,7 @@ pour faire cette activité sur votre ordi localement, {download}`commencez par t
2520

2621
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
2722

28-
```{image} media/init3.png
23+
```{image} media/init3-1.png
2924
:align: center
3025
:width: 600px
3126
```
@@ -372,13 +367,14 @@ masses, positions, speeds = init3()
372367
simulation3 = simulate(masses, positions, speeds, nb_steps=steps)
373368
draw(simulation3, masses, colors3);
374369
375-
plt.savefig("init3.png")
376-
np.savetxt("simulation3.txt", simulation3.reshape(-1))
370+
plt.savefig("init3-1.png")
371+
np.savetxt("init3-simu-1.txt", simulation3.reshape(-1))
377372
print(simulation3.shape)
378373
```
379374

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

384380
+++
@@ -433,6 +429,42 @@ voyez un peu si vous arrivez à produire un outil un peu plus convivial pour exp
433429

434430
voici une possibilité avec matplotlib; mais cela dit ne vous sentez pas obligé de rester dans Jupyter Lab ou matplotlib, il y a plein de technos rigolotes qui savent se décliner sur le web, vous avez l'embarras du choix...
435431

432+
+++
433+
434+
(label-n-body-strategies)=
435+
## plusieurs stratégies
436+
437+
Pour les curieux, vous avez sans doute observé qu'il y a plusieurs façons possibles d'écrire la fonction `simulate()`
438+
439+
dans ce qui suit, on note l'accélération $a$, la vitesse $s$ et la position $p$
440+
441+
````{admonition} Option 1
442+
:class: tip
443+
dans l'implémentation qui a servi à calculer l'illustration ci-dessus, on a écrit principalement ceci:
444+
- on calcule l'accélération
445+
- ce qui permet d'extrapoler les vitesses
446+
$s = s + a.dt$
447+
- et ensuite d'extrapoler les positions
448+
$p = p + s.dt$
449+
```{admonition} 1bis
450+
:class: dropdown
451+
une variante consiste à intervertir les deux, en arguant du fait que la vitesse à l'instant $t$ agit sur la position à l'instant $t$
452+
- on extrapole d'abord les positions
453+
- puis seulement on calcule l'accélération
454+
- enfin on extrapole les vitesses
455+
```
456+
````
457+
````{admonition} Option 2
458+
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 à
459+
- calculer la position comme $p = p + s.dt + \frac{a}{2}.dt^2$
460+
- calculer les accélérations $a_+$ sur la base de cette nouvelle position
461+
- estimer l'accélération sur l'intervalle comme la demie-somme entre les deux accélérations
462+
$a_m = (a+a_+)/2$
463+
- mettre à jour les vitesses
464+
$s = s + a_m.dt$
465+
- ranger $a_+$ dans $a$ pour le prochain instant
466+
````
467+
436468
```{code-cell} ipython3
437469
:tags: [prune-remove-input]
438470
@@ -466,13 +498,138 @@ def animate(simulation, masses, colors=None, scale=5., interval=50):
466498
scat.set_offsets(np.c_[x, y])
467499
return scat
468500
469-
ani = FuncAnimation(fig, update, frames=nb_steps,
470-
init_func=init, blit=True, interval=interval)
501+
animation = FuncAnimation(
502+
fig, update, frames=nb_steps,
503+
init_func=init, blit=True, interval=interval
504+
)
471505
plt.close()
472-
return ani
506+
return animation
473507
508+
def animate_from_file(filename):
509+
simulation = np.loadtxt(filename).reshape((100, 2, 3))
510+
animation = animate(simulation, masses, colors=colors3)
511+
return HTML(animation.to_jshtml())
474512
475-
simulation3 = np.loadtxt("data/simulation3.txt").reshape((100, 2, 3))
476-
animation = animate(simulation3, masses, colors=colors3)
477-
HTML(animation.to_jshtml())
513+
animate_from_file("data/init3-simu-1.txt")
478514
```
515+
516+
Bref, vous voyez qu'il y a énormément de liberté sur la façon de s'y prendre
517+
Ce qui peut expliquer pourquoi vous n'obtenez pas la même chose que les illustrations avec pourtant les mêmes données initiales
518+
519+
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
520+
521+
+++
522+
523+
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 !
524+
525+
+++
526+
527+
### options 1bis
528+
529+
```{code-cell} ipython3
530+
# prune-cell
531+
532+
def simulate_1bis(masses, positions, speeds, dt=0.1, nb_steps=100):
533+
# initialize result
534+
N = masses.shape[0]
535+
result = np.empty((nb_steps, 2, N))
536+
# store initial position
537+
result[0] = positions
538+
539+
# because we have stored the initial state
540+
# we need to start at 1 here, we dont have space for more
541+
for step_counter in range(1, nb_steps):
542+
# apply on positions
543+
positions += dt*speeds
544+
545+
# compute forces and accelerations
546+
accelerations = forces(masses, positions) / masses
547+
548+
# apply on speeds
549+
speeds += dt*accelerations
550+
551+
# store current position
552+
result[step_counter] = positions
553+
return result
554+
555+
```
556+
557+
```{code-cell} ipython3
558+
# prune-cell
559+
560+
steps = 100
561+
562+
masses, positions, speeds = init3()
563+
simulation3 = simulate_1bis(masses, positions, speeds, nb_steps=steps)
564+
draw(simulation3, masses, colors3);
565+
566+
plt.savefig("init3-1bis.png")
567+
np.savetxt("init3-simu-1bis.txt", simulation3.reshape(-1))
568+
print(simulation3.shape)
569+
```
570+
571+
````{grid} 2 2 2 2
572+
```{image} media/init3-1bis.png
573+
```
574+
```{code-cell} python
575+
:tags: [remove-input]
576+
animate_from_file("data/init3-simu-1bis.txt")
577+
```
578+
````
579+
580+
+++
581+
582+
### option 2
583+
584+
```{code-cell} ipython3
585+
# prune-cell
586+
587+
def simulate_2(masses, positions, speeds, dt=0.1, nb_steps=100):
588+
# initialize result
589+
N = masses.shape[0]
590+
result = np.empty((nb_steps, 2, N))
591+
# store initial position
592+
result[0] = positions
593+
594+
# initialize accelerations
595+
accelerations = forces(masses, positions) / masses
596+
597+
# because we have stored the initial state
598+
# we need to start at 1 here, we dont have space for more
599+
for step_counter in range(1, nb_steps):
600+
# compute positions
601+
positions = positions + speeds * dt + accelerations * dt**2 * 0.5
602+
# compute forces and accelerations for next instant
603+
next_accelerations = forces(masses, positions) / masses
604+
# use half-mean as an approximation of acceleration over the period
605+
speeds += 0.5 * (accelerations + next_accelerations)/2 * dt
606+
# save for next iteration
607+
accelerations = next_accelerations
608+
609+
# store current position
610+
result[step_counter] = positions
611+
return result
612+
```
613+
614+
```{code-cell} ipython3
615+
# prune-cell
616+
617+
steps = 100
618+
619+
masses, positions, speeds = init3()
620+
simulation3 = simulate_2(masses, positions, speeds, nb_steps=steps)
621+
draw(simulation3, masses, colors3);
622+
623+
plt.savefig("init3-2.png")
624+
np.savetxt("init3-simu-2.txt", simulation3.reshape(-1))
625+
print(simulation3.shape)
626+
```
627+
628+
````{grid} 2 2 2 2
629+
```{image} media/init3-2.png
630+
```
631+
```{code-cell} python
632+
:tags: [remove-input]
633+
animate_from_file("data/init3-simu-2.txt")
634+
```
635+
````
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: 89 additions & 16 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:
@@ -11,10 +8,6 @@ kernelspec:
118
name: python3
129
display_name: Python 3 (ipykernel)
1310
language: python
14-
language_info:
15-
name: python
16-
pygments_lexer: ipython3
17-
nbconvert_exporter: python
1811
---
1912

2013
# n-body problem
@@ -25,7 +18,7 @@ pour faire cette activité sur votre ordi localement, {download}`commencez par t
2518

2619
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
2720

28-
```{image} media/init3.png
21+
```{image} media/init3-1.png
2922
:align: center
3023
:width: 600px
3124
```
@@ -258,8 +251,9 @@ pour commencer et tester, on se met dans l'état initial reproductible
258251
# draw(simulate(masses, positions, speeds), masses, colors3)
259252
```
260253

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

265259
+++
@@ -314,6 +308,42 @@ voyez un peu si vous arrivez à produire un outil un peu plus convivial pour exp
314308

315309
voici une possibilité avec matplotlib; mais cela dit ne vous sentez pas obligé de rester dans Jupyter Lab ou matplotlib, il y a plein de technos rigolotes qui savent se décliner sur le web, vous avez l'embarras du choix...
316310

311+
+++
312+
313+
(label-n-body-strategies)=
314+
## plusieurs stratégies
315+
316+
Pour les curieux, vous avez sans doute observé qu'il y a plusieurs façons possibles d'écrire la fonction `simulate()`
317+
318+
dans ce qui suit, on note l'accélération $a$, la vitesse $s$ et la position $p$
319+
320+
````{admonition} Option 1
321+
:class: tip
322+
dans l'implémentation qui a servi à calculer l'illustration ci-dessus, on a écrit principalement ceci:
323+
- on calcule l'accélération
324+
- ce qui permet d'extrapoler les vitesses
325+
$s = s + a.dt$
326+
- et ensuite d'extrapoler les positions
327+
$p = p + s.dt$
328+
```{admonition} 1bis
329+
:class: dropdown
330+
une variante consiste à intervertir les deux, en arguant du fait que la vitesse à l'instant $t$ agit sur la position à l'instant $t$
331+
- on extrapole d'abord les positions
332+
- puis seulement on calcule l'accélération
333+
- enfin on extrapole les vitesses
334+
```
335+
````
336+
````{admonition} Option 2
337+
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 à
338+
- calculer la position comme $p = p + s.dt + \frac{a}{2}.dt^2$
339+
- calculer les accélérations $a_+$ sur la base de cette nouvelle position
340+
- estimer l'accélération sur l'intervalle comme la demie-somme entre les deux accélérations
341+
$a_m = (a+a_+)/2$
342+
- mettre à jour les vitesses
343+
$s = s + a_m.dt$
344+
- ranger $a_+$ dans $a$ pour le prochain instant
345+
````
346+
317347
```{code-cell} ipython3
318348
:tags: [prune-remove-input, remove-input]
319349
@@ -347,13 +377,56 @@ def animate(simulation, masses, colors=None, scale=5., interval=50):
347377
scat.set_offsets(np.c_[x, y])
348378
return scat
349379
350-
ani = FuncAnimation(fig, update, frames=nb_steps,
351-
init_func=init, blit=True, interval=interval)
380+
animation = FuncAnimation(
381+
fig, update, frames=nb_steps,
382+
init_func=init, blit=True, interval=interval
383+
)
352384
plt.close()
353-
return ani
385+
return animation
354386
387+
def animate_from_file(filename):
388+
simulation = np.loadtxt(filename).reshape((100, 2, 3))
389+
animation = animate(simulation, masses, colors=colors3)
390+
return HTML(animation.to_jshtml())
355391
356-
simulation3 = np.loadtxt("data/simulation3.txt").reshape((100, 2, 3))
357-
animation = animate(simulation3, masses, colors=colors3)
358-
HTML(animation.to_jshtml())
392+
animate_from_file("data/init3-simu-1.txt")
359393
```
394+
395+
Bref, vous voyez qu'il y a énormément de liberté sur la façon de s'y prendre
396+
Ce qui peut expliquer pourquoi vous n'obtenez pas la même chose que les illustrations avec pourtant les mêmes données initiales
397+
398+
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
399+
400+
+++
401+
402+
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 !
403+
404+
+++
405+
406+
### options 1bis
407+
408+
+++
409+
410+
````{grid} 2 2 2 2
411+
```{image} media/init3-1bis.png
412+
```
413+
```{code-cell} python
414+
:tags: [remove-input]
415+
animate_from_file("data/init3-simu-1bis.txt")
416+
```
417+
````
418+
419+
+++
420+
421+
### option 2
422+
423+
+++
424+
425+
````{grid} 2 2 2 2
426+
```{image} media/init3-2.png
427+
```
428+
```{code-cell} python
429+
:tags: [remove-input]
430+
animate_from_file("data/init3-simu-2.txt")
431+
```
432+
````
File renamed without changes.

0 commit comments

Comments
 (0)