2023-10-07 22:47:13 +00:00
|
|
|
import numpy as np
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectileSimulation:
|
2023-10-07 19:57:32 +00:00
|
|
|
"""
|
|
|
|
A class used to simulate the motion of a projectile with air resistance.
|
|
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
Attributes
|
|
|
|
----------
|
|
|
|
g : float
|
|
|
|
acceleration due to gravity (m/s^2)
|
|
|
|
C_d : float
|
|
|
|
drag coefficient
|
|
|
|
v_init : float
|
|
|
|
initial velocity (m/s)
|
|
|
|
theta : float
|
|
|
|
launch angle (degrees to radians)
|
|
|
|
h_init : float
|
|
|
|
launch height (m)
|
|
|
|
dt : float
|
|
|
|
time step (s)
|
|
|
|
t_end : float
|
|
|
|
total time of simulation (s)
|
|
|
|
|
|
|
|
Methods
|
|
|
|
-------
|
|
|
|
reset():
|
|
|
|
Resets the simulation to its initial state.
|
|
|
|
calculate_acceleration(t, state):
|
|
|
|
Calculates the acceleration at a given time and state.
|
|
|
|
update_state(t, state):
|
|
|
|
Updates the state using the Runge-Kutta method.
|
|
|
|
run():
|
|
|
|
Runs the simulation.
|
|
|
|
plot():
|
|
|
|
Plots the trajectory of the projectile.
|
|
|
|
print_range():
|
|
|
|
Prints the range of the projectile.
|
|
|
|
"""
|
|
|
|
|
2023-10-07 22:47:13 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
g=9.81,
|
|
|
|
C_d=0.01,
|
|
|
|
v_init=100,
|
|
|
|
theta=np.radians(180),
|
|
|
|
h_init=10,
|
|
|
|
dt=0.01,
|
|
|
|
t_end=10,
|
|
|
|
):
|
|
|
|
self.g = g
|
|
|
|
self.C_d = C_d
|
|
|
|
self.v_init = v_init
|
|
|
|
self.theta = theta
|
|
|
|
self.h_init = h_init
|
|
|
|
self.dt = dt
|
|
|
|
self.t_end = t_end
|
|
|
|
self.reset()
|
|
|
|
|
|
|
|
def reset(self):
|
2023-10-07 19:57:32 +00:00
|
|
|
"""Resets the simulation to its initial state."""
|
2023-10-07 22:47:13 +00:00
|
|
|
self.x = [0]
|
|
|
|
self.y = [self.h_init]
|
|
|
|
self.vx = [self.v_init * np.cos(self.theta)]
|
|
|
|
self.vy = [self.v_init * np.sin(self.theta)]
|
|
|
|
self.t = [0]
|
|
|
|
|
|
|
|
def calculate_acceleration(self, t, state):
|
2023-10-07 19:57:32 +00:00
|
|
|
"""Calculates the acceleration at a given time and state."""
|
2023-10-07 22:47:13 +00:00
|
|
|
x, y, vx, vy = state
|
|
|
|
v = np.sqrt(vx**2 + vy**2)
|
|
|
|
F_air_x = -self.C_d * v * vx
|
|
|
|
F_air_y = -self.C_d * v * vy
|
|
|
|
ax = F_air_x
|
|
|
|
ay = F_air_y - self.g
|
|
|
|
return [vx, vy, ax, ay]
|
|
|
|
|
|
|
|
def update_state(self, t, state):
|
2023-10-07 19:57:32 +00:00
|
|
|
"""Updates the state using the Runge-Kutta method."""
|
2023-10-07 22:47:13 +00:00
|
|
|
k1 = self.calculate_acceleration(t, state)
|
|
|
|
k2 = self.calculate_acceleration(
|
|
|
|
t + 0.5 * self.dt, [s + 0.5 * self.dt * k for s, k in zip(state, k1)]
|
|
|
|
)
|
|
|
|
k3 = self.calculate_acceleration(
|
|
|
|
t + 0.5 * self.dt, [s + 0.5 * self.dt * k for s, k in zip(state, k2)]
|
|
|
|
)
|
|
|
|
k4 = self.calculate_acceleration(
|
|
|
|
t + self.dt, [s + self.dt * k for s, k in zip(state, k3)]
|
|
|
|
)
|
|
|
|
return [
|
|
|
|
s + self.dt / 6 * (k1_i + 2 * k2_i + 2 * k3_i + k4_i)
|
|
|
|
for s, k1_i, k2_i, k3_i, k4_i in zip(state, k1, k2, k3, k4)
|
|
|
|
]
|
|
|
|
|
|
|
|
def run(self):
|
2023-10-07 19:57:32 +00:00
|
|
|
"""Runs the simulation."""
|
2023-10-07 22:47:13 +00:00
|
|
|
while self.t[-1] < self.t_end:
|
|
|
|
state = [self.x[-1], self.y[-1], self.vx[-1], self.vy[-1]]
|
|
|
|
state = self.update_state(self.t[-1], state)
|
|
|
|
self.x.append(state[0])
|
|
|
|
self.y.append(state[1])
|
|
|
|
self.vx.append(state[2])
|
|
|
|
self.vy.append(state[3])
|
|
|
|
self.t.append(self.t[-1] + self.dt)
|
|
|
|
|
|
|
|
def plot(self):
|
2023-10-07 19:57:32 +00:00
|
|
|
"""Plots the trajectory of the projectile."""
|
2023-10-07 22:47:13 +00:00
|
|
|
plt.plot(self.x, self.y)
|
|
|
|
plt.xlabel("Horizontal distance (m)")
|
|
|
|
plt.ylabel("Vertical distance (m)")
|
|
|
|
plt.title("Projectile Motion with Air Resistance")
|
|
|
|
plt.show()
|
|
|
|
|
|
|
|
def print_range(self):
|
2023-10-07 19:57:32 +00:00
|
|
|
"""Prints the range of the projectile."""
|
2023-10-07 22:47:13 +00:00
|
|
|
range_projectile = self.x[-1]
|
|
|
|
print(f"Range of projectile: {range_projectile:.2f} m")
|