Skip to content

Commit 681d30c

Browse files
authored
reupload
1 parent 1367811 commit 681d30c

File tree

1 file changed

+250
-0
lines changed

1 file changed

+250
-0
lines changed

other/n_body_simulation.py

+250
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
"""
2+
In physics and astronomy, a gravitational N-body simulation is a simulation of a
3+
dynamical system of particles under the influence of gravity. The system
4+
consists of a number of bodies, each of which exerts a gravitational force on all
5+
other bodies. These forces are calculated using Newton's law of universal
6+
gravitation. The Euler method is used at each time-step to calculate the change in
7+
velocity and position brought about by these forces. Softening is used to prevent
8+
numerical divergences when a particle comes too close to another (and the force
9+
goes to infinity).
10+
(Description adapted from https://en.wikipedia.org/wiki/N-body_simulation )
11+
(See also http://www.shodor.org/refdesk/Resources/Algorithms/EulersMethod/ )
12+
"""
13+
14+
15+
from __future__ import annotations
16+
17+
import random
18+
19+
from matplotlib import animation # type: ignore
20+
from matplotlib import pyplot as plt
21+
22+
23+
class Body:
24+
def __init__(
25+
self: Body,
26+
position_x: float,
27+
position_y: float,
28+
velocity_x: float,
29+
velocity_y: float,
30+
mass: float = 1.0,
31+
size: float = 1.0,
32+
color: str = "blue",
33+
) -> None:
34+
"""
35+
The parameters "size" & "color" are not relevant for the simulation itself,
36+
they are only used for plotting.
37+
"""
38+
self.position_x = position_x
39+
self.position_y = position_y
40+
self.velocity_x = velocity_x
41+
self.velocity_y = velocity_y
42+
self.mass = mass
43+
self.size = size
44+
self.color = color
45+
46+
def update_velocity(
47+
self: Body, force_x: float, force_y: float, delta_time: float
48+
) -> None:
49+
"""
50+
Euler algorithm for velocity
51+
52+
>>> body = Body(0.,0.,0.,0.)
53+
>>> body.update_velocity(1.,0.,1.)
54+
>>> body.velocity_x
55+
1.0
56+
"""
57+
self.velocity_x += force_x * delta_time
58+
self.velocity_y += force_y * delta_time
59+
60+
def update_position(self: Body, delta_time: float) -> None:
61+
"""
62+
Euler algorithm for position
63+
64+
>>> body = Body(0.,0.,1.,0.)
65+
>>> body.update_position(1.)
66+
>>> body.position_x
67+
1.0
68+
"""
69+
self.position_x += self.velocity_x * delta_time
70+
self.position_y += self.velocity_y * delta_time
71+
72+
73+
class BodySystem:
74+
"""
75+
This class is used to hold the bodies, the gravitation constant, the time
76+
factor and the softening factor. The time factor is used to control the speed
77+
of the simulation. The softening factor is used for softening, a numerical
78+
trick for N-body simulations to prevent numerical divergences when two bodies
79+
get too close to each other.
80+
"""
81+
82+
def __init__(
83+
self: BodySystem,
84+
bodies: list[Body],
85+
gravitation_constant: float = 1.0,
86+
time_factor: float = 1.0,
87+
softening_factor: float = 0.0,
88+
) -> None:
89+
self.bodies = bodies
90+
self.gravitation_constant = gravitation_constant
91+
self.time_factor = time_factor
92+
self.softening_factor = softening_factor
93+
94+
def update_system(self: BodySystem, delta_time: float) -> None:
95+
"""
96+
For each body, loop through all other bodies to calculate the total
97+
force they exert on it. Use that force to update the body's velocity.
98+
99+
>>> body_system = BodySystem([Body(0,0,0,0), Body(10,0,0,0)])
100+
>>> body_system.update_system(1)
101+
>>> body_system.bodies[0].position_x
102+
0.01
103+
"""
104+
for body1 in self.bodies:
105+
force_x = 0.0
106+
force_y = 0.0
107+
for body2 in self.bodies:
108+
if body1 != body2:
109+
dif_x = body2.position_x - body1.position_x
110+
dif_y = body2.position_y - body1.position_y
111+
112+
# Calculation of the distance using Pythagoras's theorem
113+
# Extra factor due to the softening technique
114+
distance = (dif_x ** 2 + dif_y ** 2 + self.softening_factor) ** (
115+
1 / 2
116+
)
117+
118+
# Newton's law of universal gravitation.
119+
force_x += (
120+
self.gravitation_constant * body2.mass * dif_x / distance ** 3
121+
)
122+
force_y += (
123+
self.gravitation_constant * body2.mass * dif_y / distance ** 3
124+
)
125+
126+
# Update the body's velocity once all the force components have been added
127+
body1.update_velocity(force_x, force_y, delta_time * self.time_factor)
128+
129+
# Update the positions only after all the velocities have been updated
130+
for body in self.bodies:
131+
body.update_position(delta_time * self.time_factor)
132+
133+
134+
def plot(
135+
title: str,
136+
body_system: BodySystem,
137+
x_start: float = -1,
138+
x_end: float = 1,
139+
y_start: float = -1,
140+
y_end: float = 1,
141+
) -> None:
142+
INTERVAL = 20 # Frame rate of the animation
143+
DELTA_TIME = INTERVAL / 1000 # Time between time steps in seconds
144+
145+
fig = plt.figure()
146+
fig.canvas.set_window_title(title)
147+
148+
# Set section to be plotted
149+
ax = plt.axes(xlim=(x_start, x_end), ylim=(y_start, y_end))
150+
151+
# Each body is drawn as a patch by the plt-function
152+
patches = []
153+
for body in body_system.bodies:
154+
patches.append(
155+
plt.Circle((body.position_x, body.position_y), body.size, fc=body.color)
156+
)
157+
158+
# Function called once at the start of the animation
159+
def init() -> list[patches.Circle]: # type: ignore
160+
axes = plt.gca()
161+
axes.set_aspect("equal")
162+
163+
for patch in patches:
164+
ax.add_patch(patch)
165+
return patches
166+
167+
# Function called at each step of the animation
168+
def animate(i: int) -> list[patches.Circle]: # type: ignore
169+
# Update the positions of the bodies
170+
body_system.update_system(DELTA_TIME)
171+
172+
# Update the positions of the patches
173+
for patch, body in zip(patches, body_system.bodies):
174+
patch.center = (body.position_x, body.position_y)
175+
return patches
176+
177+
anim = animation.FuncAnimation( # noqa: F841
178+
fig, animate, init_func=init, interval=INTERVAL, blit=True
179+
)
180+
181+
plt.show()
182+
183+
184+
if __name__ == "__main__":
185+
# Example 1: figure-8 solution to the 3-body-problem
186+
position_x = 0.9700436
187+
position_y = -0.24308753
188+
velocity_x = 0.466203685
189+
velocity_y = 0.43236573
190+
191+
bodies1 = [
192+
Body(position_x, position_y, velocity_x, velocity_y, size=0.2, color="red"),
193+
Body(-position_x, -position_y, velocity_x, velocity_y, size=0.2, color="green"),
194+
Body(0, 0, -2 * velocity_x, -2 * velocity_y, size=0.2, color="blue"),
195+
]
196+
body_system1 = BodySystem(bodies1, time_factor=3)
197+
plot("Figure-8 solution to the 3-body-problem", body_system1, -2, 2, -2, 2)
198+
199+
# Example 2: Moon's orbit around the earth
200+
moon_mass = 7.3476e22
201+
earth_mass = 5.972e24
202+
velocity_dif = 1022
203+
earth_moon_distance = 384399000
204+
gravitation_constant = 6.674e-11
205+
206+
# Calculation of the respective velocities so that total impulse is zero,
207+
# i.e. the two bodies together don't move
208+
moon_velocity = earth_mass * velocity_dif / (earth_mass + moon_mass)
209+
earth_velocity = moon_velocity - velocity_dif
210+
211+
moon = Body(-earth_moon_distance, 0, 0, moon_velocity, moon_mass, 10000000, "grey")
212+
earth = Body(0, 0, 0, earth_velocity, earth_mass, 50000000, "blue")
213+
body_system2 = BodySystem([earth, moon], gravitation_constant, time_factor=1000000)
214+
plot(
215+
"Moon's orbit around the earth",
216+
body_system2,
217+
-430000000,
218+
430000000,
219+
-430000000,
220+
430000000,
221+
)
222+
223+
# Example 3: Random system with many bodies
224+
bodies = []
225+
for i in range(10):
226+
velocity_x = random.uniform(-0.5, 0.5)
227+
velocity_y = random.uniform(-0.5, 0.5)
228+
229+
# Bodies are created pairwise with opposite velocities so that the
230+
# total impulse remains zero
231+
bodies.append(
232+
Body(
233+
random.uniform(-0.5, 0.5),
234+
random.uniform(-0.5, 0.5),
235+
velocity_x,
236+
velocity_y,
237+
size=0.05,
238+
)
239+
)
240+
bodies.append(
241+
Body(
242+
random.uniform(-0.5, 0.5),
243+
random.uniform(-0.5, 0.5),
244+
-velocity_x,
245+
-velocity_y,
246+
size=0.05,
247+
)
248+
)
249+
body_system3 = BodySystem(bodies, 0.01, 10, 0.1)
250+
plot("Random system with many bodies", body_system3, -1.5, 1.5, -1.5, 1.5)

0 commit comments

Comments
 (0)