Back to Home

Modeling external ballistics: analytics and Python

The article analyzes analytical and numerical methods for solving the external ballistics problem. Cases without resistance, with linear and quadratic laws are considered. Full Python simulator code using SciPy for trajectory calculations is provided.

External ballistics: from Euler to Python simulator
Advertisement 728x90

Analytical and Numerical Modeling of External Ballistics: Three Key Cases

The external ballistics problem describes the planar motion of a body under gravity, launched at an angle to the horizontal with an initial velocity. Three cases are considered: no air resistance, linear resistance according to Stokes' law, and quadratic resistance following Newton's law. Analytical solutions are complemented by numerical methods for realistic simulation.

In the first case—no resistance—the trajectory is a parabola. For v₀ = 20 m/s and α = 30°, range and height are determined by simple formulas:

x(t) = v₀ cos(α) t

Google AdInline article slot

y(t) = v₀ sin(α) t - (g t²)/2

Linear Resistance: Stokes' Law

With linear resistance, force is proportional to velocity: F = -k v. The differential equations are:

m dv_x/dt = -k v_x

Google AdInline article slot

m dv_y/dt = -m g - k v_y

The solution yields exponential velocity dependencies:

v_x(t) = v₀ cos(α) e^(-kt/m)

Google AdInline article slot

v_y(t) = (v₀ sin(α) + (m g)/k) e^(-kt/m) - (m g)/k

Integrating these gives y(x). For v₀ = 70 m/s, α = 55°, k = 0.04 kg/s, m = 0.5 kg, the trajectory becomes asymmetric with reduced range compared to the ideal case.

Quadratic Resistance: Newton's Law

At high speeds, quadratic resistance dominates: F = -k v². The system of ODEs is:

dv_x/dt = -k v v_x

dv_y/dt = -g - k v v_y

where v = √(v_x² + v_y²). Leonhard Euler solved this system analytically in the 18th century. Today’s approach uses numerical integration via Runge-Kutta order 4–5.

import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp

class BallisticsSimulator:
    def __init__(self):
        self.g = 9.81
        self.rho = 1.225

    def calculate_k(self, diameter, mass, Cd=0.3):
        A = np.pi * (diameter / 2) ** 2
        k = (Cd * self.rho * A) / (2 * mass)
        return k

    def equations_of_motion(self, t, state, k):
        x, vx, y, vy = state
        v = np.sqrt(vx**2 + vy**2)
        ax = -k * v * vx
        ay = -self.g - k * v * vy
        return [vx, ax, vy, ay]

    def simulate(self, v0, angle_deg, x0=0, y0=0,
              diameter=0.1, mass=10, Cd=0.3,
              t_max=100, dt=0.01):
        k = self.calculate_k(diameter, mass, Cd)
        angle_rad = np.radians(angle_deg)
        vx0 = v0 * np.cos(angle_rad)
        vy0 = v0 * np.sin(angle_rad)
        initial_state = [x0, vx0, y0, vy0]
        t_span = (0, t_max)
        t_eval = np.arange(0, t_max, dt)
        solution = solve_ivp(
            self.equations_of_motion,
            t_span,
            initial_state,
            args=(k,),
            t_eval=t_eval,
            method='RK45'
        )
        t = solution.t
        x = solution.y[0]
        y = solution.y[2]
        fall_index = np.where(y < 0)[0]
        if len(fall_index) > 0:
            fall_index = fall_index[0]
            t = t[:fall_index + 1]
            x = x[:fall_index + 1]
            y = y[:fall_index + 1]
        return t, x, y, k

    def plot_trajectory(self, t, x, y, k, v0, angle):
        plt.figure(figsize=(12, 8))
        plt.plot(x, y, 'b-', linewidth=2, label='Trajectory')
        plt.scatter(x[0], y[0], color='red', s=100, zorder=5, label='Launch')
        plt.scatter(x[-1], y[-1], color='green', s=100, zorder=5, label='Impact')
        plt.xlabel('Distance, m', fontsize=12)
        plt.ylabel('Height, m', fontsize=12)
        plt.title(f'Shot Trajectory\nV₀ = {v0} m/s, angle = {angle}°, k = {k:.6f}',
                  fontsize=14)
        plt.grid(True, alpha=0.3)
        plt.legend()
        plt.axis('equal')
        plt.show()

    def print_results(self, t, x, y):
        print("=== SIMULATION RESULTS ===")
        print(f"Range: {x[-1]:.2f} m")
        print(f"Maximum height: {max(y):.2f} m")
        print(f"Flight time: {t[-1]:.2f} s")

# Example: v0=250 m/s, angle=60°, d=0.2 m, m=20 kg

For v₀ = 250 m/s, α = 60°, diameter 20 cm, mass 20 kg, Cd = 0.3, the coefficient k is calculated as k = (Cd ρ A)/(2m), where A is the cross-sectional area. The solve_ivp function with RK45 method ensures accuracy up to impact (y = 0).

Simulation Parameters and Results

Key input parameters:

  • Initial velocity v₀: 20–250 m/s
  • Launch angle α: 30–60°
  • Mass m: 0.5–20 kg
  • Diameter: 10–20 cm
  • Cd: 0.3 (typical value)
  • k: automatically computed

Results for the quadratic case:

  • Flight range: ~10–20 km (depends on parameters)
  • Maximum altitude: 5–15 km
  • Flight time: 50–100 seconds
  • Final speed: close to terminal velocity

Comparing cases shows a 30–50% reduction in range when air resistance is accounted for.

Key Takeaways

  • No resistance → parabolic trajectory; maximum range at α = 45°.
  • Stokes’ law applies to low speeds (<30 m/s), yielding exponential decay.
  • Newton’s law is standard for artillery, requiring numerical ODE solving.
  • Python code using scipy.integrate is versatile and easily adapted to real-world Cd and ρ values.

— Editorial Team

Advertisement 728x90

Read Next