<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>TinyComputers.io (Posts about matplotlib)</title><link>https://tinycomputers.io/</link><description></description><atom:link href="https://tinycomputers.io/categories/matplotlib.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Original site content © 2022–2026 Tiny Machines Workshop, LLC, except where otherwise noted. Some rights reserved.</copyright><lastBuildDate>Sat, 26 Sep 2026 18:52:26 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Optimizing Scientific Simulations: JAX-Powered Ballistic Calculations</title><link>https://tinycomputers.io/posts/optimizing-scientific-simulations-jax-powered-ballistic-calculations.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/optimizing-scientific-simulations-jax-powered-ballistic-calculations_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;23 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;h3&gt;Introduction to Projectile Simulation and Modern Python Tools&lt;/h3&gt;
&lt;p&gt;Accurate simulation of projectile motion is a cornerstone of engineering, ballistics, and numerous scientific fields. Advanced simulations empower engineers and researchers to design better projectiles, optimize firing solutions, and visualize real-world outcomes before physical testing. In the modern age, computational power and flexible programming tools have transformed the landscape: what once required specialized software or labor-intensive calculations can now be accomplished interactively and at scale, right from within a Python environment.&lt;/p&gt;
&lt;p&gt;If you’ve explored our previous article on the fundamental physics governing projectile motion (including forces, air resistance, and drag models), you’re already equipped with the core theoretical background. Now it’s time to bridge theory and application.&lt;/p&gt;
&lt;p&gt;This post is a hands-on guide to building a complete, end-to-end simulation of projectile trajectories in Python, harnessing &lt;a href="https://go.tinymachines.io/pA4xzt"&gt;JAX&lt;/a&gt;, a state-of-the-art computational library. JAX brings together automatic differentiation, just-in-time (JIT) compilation, and accelerated linear algebra, enabling lightning-fast simulation of complex scientific systems. The focus will be less on the physics itself (already well covered) and more on translating those equations into robust, performant code.&lt;/p&gt;
&lt;p&gt;You’ll see how to set up the necessary equations, efficiently solve them using modern ODE integration tools, and visualize the results, all while leveraging JAX’s unique features for speed and scalability. Whether you’re a ballistics enthusiast, an engineer, or a scientific Python user eager to level up, this walk-through will arm you with tools and practices that apply far beyond just projectile simulation.&lt;/p&gt;
&lt;p&gt;Let’s dive in and see how modern Python changes the game for scientific simulation!&lt;/p&gt;
&lt;h3&gt;Overview: Problem Setup and Simulation Goals&lt;/h3&gt;
&lt;p&gt;In this section, we set the stage for our ballistic simulation, clarifying what we’re modeling, why it matters, and the practical outcomes we seek to extract from the code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is being simulated?&lt;/strong&gt;&lt;br&gt;
The core objective is to simulate the flight of a projectile (in this case, a typical 5.56 mm round) fired from a set initial height and velocity. The code models its motion under the influence of gravity and aerodynamic drag, capturing the trajectory as it travels horizontally towards a target positioned at a specific range, say, 500 meters. The simulation starts at the muzzle of the firearm, positioned at a given height above the ground, and traces the projectile’s path through the air until it either impacts the ground or reaches beyond the target.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why simulate?&lt;/strong&gt;&lt;br&gt;
Such simulations are invaluable for answering “what-if” questions in projectile design and use: what if I change the muzzle velocity? How does a heavier or lighter round perform? At what angle should I aim to hit a given target at a certain distance? This approach enables users to tweak parameters and instantly gauge the impact, eliminating guesswork and excessive field testing. For both professionals and enthusiasts, it’s a chance to iterate on design and tactics within minutes, not months.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are the desired outputs?&lt;/strong&gt;&lt;br&gt;
Our main outputs include:
- The full trajectory curve of the projectile (height vs. range)
- The precise launch angle required to hit a specified target distance
- Visualizations to help interpret and communicate simulation results&lt;/p&gt;
&lt;p&gt;Together, these outputs empower informed decision-making and deeper insight into ballistic performance, all driven by robust computational modeling.&lt;/p&gt;
&lt;p&gt;It appears that JAX (a core library for this simulation) is not available in the current environment, which prevents execution of the code involving JAX.&lt;/p&gt;
&lt;p&gt;However, I will proceed with a detailed narrative for this section, focusing on key implementation concepts, code structure, and modularity, backed with illustrative (but non-executable) code snippets:&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Building the ODE System in Python&lt;/h3&gt;
&lt;p&gt;A robust simulation relies on clear formulation and modular code. Here’s how we set up the ordinary differential equation (ODE) problem for projectile motion in Python:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;State Vector Choice&lt;/strong&gt;&lt;br&gt;
To simulate projectile motion, we track both position and velocity in two dimensions:
- Horizontal position (&lt;code&gt;x&lt;/code&gt;)
- Vertical position (&lt;code&gt;z&lt;/code&gt;)
- Horizontal velocity (&lt;code&gt;vx&lt;/code&gt;)
- Vertical velocity (&lt;code&gt;vz&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;So, our state vector is:&lt;br&gt;
&lt;code&gt;y = [x, z, vx, vz]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This compact representation allows for versatile modeling and easy extension (e.g., adding wind, spin, or more dimensions).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Constructing the System of Differential Equations&lt;/strong&gt;&lt;br&gt;
Projectile motion is governed by Newton’s laws, capturing how forces (gravity, drag) influence velocity, and how velocity updates position:
- &lt;code&gt;dx/dt = vx&lt;/code&gt;
- &lt;code&gt;dz/dt = vz&lt;/code&gt;
- &lt;code&gt;dvx/dt = -drag_x / m&lt;/code&gt;
- &lt;code&gt;dvz/dt = gravity - drag_z / m&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Drag is a velocity-dependent force that always acts opposite to the direction of movement. The code calculates its magnitude and then decomposes it into x and z components.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Separating the ODE Right-Hand Side (RHS) Functionally&lt;/strong&gt;&lt;br&gt;
The core computation is wrapped in a RHS function, responsible for calculating derivatives:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="n"&gt;v_mag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vx&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;vz&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1e-9&lt;/span&gt;    &lt;span class="c1"&gt;# Avoid division by zero&lt;/span&gt;
    &lt;span class="n"&gt;Cd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;drag_cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v_mag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                      &lt;span class="c1"&gt;# Drag coefficient (customizable)&lt;/span&gt;
    &lt;span class="n"&gt;Fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rho_air&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Cd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v_mag&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;   &lt;span class="c1"&gt;# Aerodynamic drag force&lt;/span&gt;
    &lt;span class="n"&gt;ax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Fd&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;v_mag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;# Acceleration x&lt;/span&gt;
    &lt;span class="n"&gt;az&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Fd&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vz&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;v_mag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="c1"&gt;# Acceleration z&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This separation maximizes code clarity and makes performance optimizations easy (e.g., JIT compilation with JAX).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why Structure and Modularity Matter&lt;/strong&gt;&lt;br&gt;
By separating concerns (parameter setup, force models, ODE integration), you gain:
- &lt;strong&gt;Readability:&lt;/strong&gt; Each function’s purpose is clear.
- &lt;strong&gt;Testability:&lt;/strong&gt; Swap in new force or drag models to study their effect.
- &lt;strong&gt;Maintainability:&lt;/strong&gt; Code updates or physics tweaks are low-risk and contained.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Design for Expandability&lt;/strong&gt;&lt;br&gt;
A key design goal is to enable future enhancements, such as switching from a G1 drag model to a different ballistic curve, adding wind, or including non-standard forces. By passing the drag model as a function (e.g., &lt;code&gt;drag_cd = drag_cd_g1&lt;/code&gt;), you decouple physics from solver techniques.&lt;/p&gt;
&lt;p&gt;This modularity allows for rapid experimentation and testing of new models, making the simulation adaptable to various scenarios.&lt;/p&gt;
&lt;h3&gt;Setting Up the Simulation Environment&lt;/h3&gt;
&lt;p&gt;Projectile simulations are driven by several key configuration parameters that define the initial state and environment for the projectile's flight. These include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;muzzle_velocity_mps&lt;/strong&gt;: The speed at which the projectile leaves the barrel. This directly affects how far and fast the projectile travels.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;mass_kg&lt;/strong&gt;: The projectile's mass, which influences its response to drag and gravity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;muzzle_height_m&lt;/strong&gt;: The starting height above the ground. Raising the muzzle allows for a longer flight before ground impact.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;diameter_m&lt;/strong&gt; and &lt;strong&gt;air_density_kgpm3&lt;/strong&gt;: Both impact the aerodynamic drag force.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gravity_mps2&lt;/strong&gt;: The acceleration due to gravity (usually -9.80665 m/s²).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;max_time_s&lt;/strong&gt; and &lt;strong&gt;samples&lt;/strong&gt;: Define the time span and resolution for the simulation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;target_distance_m&lt;/strong&gt;: The distance to the desired target.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's best practice to set these values programmatically (using configuration dictionaries) because this approach allows for rapid adjustments, parameter sweeps, and reproducible simulations. For example, you might configure different scenarios (e.g., low velocity, high muzzle, heavy projectile) to test how changes affect trajectory and impact point.&lt;/p&gt;
&lt;p&gt;As shown in the sample table, adjusting parameters such as muzzle velocity, launch height, or projectile mass enables "what-if" analysis:&lt;br&gt;
- Lower velocity reduces range.
- Higher muzzle increases airtime and distance.
- Heavier rounds resist drag differently.&lt;/p&gt;
&lt;p&gt;This programmatic approach streamlines experimentation, ensuring that each simulation is consistent, transparent, and easily adaptable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. JAX: Accelerating Simulation and ODE Solving&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In recent years, JAX has emerged as one of the most powerful tools for scientific computing in Python. Built by Google, JAX combines the familiarity of NumPy-like syntax with transformative features for high-performance computation, making it perfectly suited to both machine learning and advanced simulation tasks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Introduction to JAX: Core Features&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At its core, JAX offers three key capabilities:
- &lt;strong&gt;Automatic Differentiation (Autograd):&lt;/strong&gt; JAX can compute gradients of code written in pure Python/Numpy-style, enabling optimization and sensitivity analysis in scientific models.
- &lt;strong&gt;XLA Compilation:&lt;/strong&gt; JAX code can be compiled just-in-time (JIT) to machine code using Google’s Accelerated Linear Algebra (XLA) backend, resulting in massive speed-ups on CPUs, GPUs, or TPUs.
- &lt;strong&gt;Pure Functions:&lt;/strong&gt; JAX enforces a functional programming style: all operations are stateless and side-effect free. This aids reproducibility, parallelism, and debugging.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why JAX is a Good Fit for Physical Simulation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Physical simulations, like the projectile ODE system here, often demand:
- Repeated evaluation of similar update steps (for integration)
- Fast turnaround for parameter studies and sweeps
- Clear-code with minimal coupling and side effects&lt;/p&gt;
&lt;p&gt;JAX’s stateless, vectorized, and parallelizable design makes it a natural fit. Its speed ups mean you can experiment more freely, running larger simulations or sampling the parameter space for optimization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How &lt;code&gt;@jit&lt;/code&gt; Compilation Speeds Up Simulation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;JAX’s &lt;code&gt;@jit&lt;/code&gt; decorator is a “just-in-time” compilation wrapper. By applying &lt;code&gt;@jit&lt;/code&gt; to your functions (such as the ODE right-hand side), JAX traces the code, compiles it to efficient machine code, and caches it for future use. For functions called thousands or millions of times (like those updating a projectile’s state at each integration step), this can yield &lt;em&gt;orders of magnitude&lt;/em&gt; speed-up over standard Python or NumPy.&lt;/p&gt;
&lt;p&gt;Example usage from the code:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;jax&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jit&lt;/span&gt;

&lt;span class="nd"&gt;@jit&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ... derivative computation ...&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dydt&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The first call to &lt;code&gt;rhs&lt;/code&gt; incurs compilation overhead, but future calls run at compiled speed. This is particularly valuable inside ODE solvers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using JAX’s &lt;code&gt;odeint&lt;/code&gt;: Syntax, Advantages, and Hardware Acceleration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While SciPy provides &lt;code&gt;scipy.integrate.odeint&lt;/code&gt; for ordinary differential equations, JAX brings its own &lt;code&gt;jax.experimental.ode.odeint&lt;/code&gt;, designed for stateless, compiled, and differentiable integration.&lt;/p&gt;
&lt;p&gt;Syntax example:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;jax.experimental.ode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;odeint&lt;/span&gt;
&lt;span class="n"&gt;traj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;odeint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tgrid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;
- &lt;strong&gt;Statelessness:&lt;/strong&gt; JAX expects pure functions, which eliminates hard-to-find bugs from global state mutations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hardware Acceleration:&lt;/strong&gt; Integrations can transparently run on GPU/TPU if available.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Differentiability:&lt;/strong&gt; Enables sensitivity analysis, parameter optimization, or training.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Seamless Integration:&lt;/strong&gt; Because both your physics (ODE) code and simulation harness share the same JAX design, everything from drag models to scoring functions can be compiled and differentiated.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Contrasting with SciPy’s ODE Solvers&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While SciPy’s &lt;code&gt;odeint&lt;/code&gt; is a powerful and widely used tool, it has limitations in terms of performance and flexibility compared to JAX. Here’s a quick comparison:&lt;/p&gt;
&lt;table border="1" style="width: 100%;" class="dataframe"&gt;
  &lt;thead&gt;
    &lt;tr style="text-align: right;"&gt;
      &lt;th&gt;Feature&lt;/th&gt;
      &lt;th&gt;SciPy (odeint)&lt;/th&gt;
      &lt;th&gt;JAX (odeint)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Backend&lt;/td&gt;
      &lt;td&gt;Python/Fortran, CPU&lt;/td&gt;
      &lt;td&gt;Compiled (XLA), GPU/TPU&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Stateful?&lt;/td&gt;
      &lt;td&gt;Yes (more impurities)&lt;/td&gt;
      &lt;td&gt;Pure functional&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Differentiable?&lt;/td&gt;
      &lt;td&gt;No (not natively)&lt;/td&gt;
      &lt;td&gt;Yes (via Autograd)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Performance&lt;/td&gt;
      &lt;td&gt;Good (CPU only)&lt;/td&gt;
      &lt;td&gt;Very high (GPU/CPU)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Debugging support&lt;/td&gt;
      &lt;td&gt;Easier, familiar&lt;/td&gt;
      &lt;td&gt;Trickier; pure code&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;Tips, Pitfalls, and Debugging When Porting ODEs to JAX&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use only JAX-aware APIs:&lt;/strong&gt; Replace NumPy (and math functions) with their &lt;code&gt;jax.numpy&lt;/code&gt; equivalents (&lt;code&gt;jnp&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Function purity:&lt;/strong&gt; Avoid side effects: no printing, mutation, or global state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Watch for unsupported types:&lt;/strong&gt; JAX functions operate on arrays, not lists or native Python scalars.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Initial compilation time:&lt;/strong&gt; The first JIT invocation is slow due to compilation overhead; don’t mistake this for actual simulation speed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugging:&lt;/strong&gt; Use the function &lt;em&gt;without&lt;/em&gt; &lt;code&gt;@jit&lt;/code&gt; for initial debugging. Once it works, add &lt;code&gt;@jit&lt;/code&gt; for speed. JAX’s error messages are improving, but complex bugs are best isolated in un-jitted code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gradual Migration:&lt;/strong&gt; If moving existing NumPy/SciPy code to JAX, port functions step by step, testing thoroughly at each stage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;JAX rewards this functional, stateless approach with unparalleled speed, scalability, and extendability. For physical simulation projects (where thousands of ODE solves may be required), JAX is a technological force-multiplier: pushing boundaries for researchers, engineers, and anyone seeking both scientific rigor and computational speed.&lt;/p&gt;
&lt;h3&gt;Numerical Simulation of Projectile Motion&lt;/h3&gt;
&lt;p&gt;The simulation of projectile motion involves several key steps, each of which is crucial for achieving accurate and reliable results. Below, we outline the process, including the mathematical formulation, numerical integration, and root-finding techniques.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Creating a Time Grid and Handling Step Size&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To integrate the equations of motion, we first discretize time into a grid. The time grid's resolution (number of samples) affects both accuracy and computational cost. In the example code, a trajectory is simulated for up to 4 seconds with 2000 sample points. This yields time steps small enough to resolve rapid changes in motion (such as during the initial phase of flight) without introducing significant numerical error or wasteful oversampling.&lt;/p&gt;
&lt;p&gt;Carefully choosing maximum simulation time and the number of points is crucial; a short simulation might end before the projectile lands, while too long or too fine a grid wastes computation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generating the Trajectory with JAX’s ODE Solver&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The simulation leverages JAX’s &lt;code&gt;odeint&lt;/code&gt; (a high-performance ODE integrator), which takes the system’s right-hand side (RHS) function, initial conditions, and the time grid. At each step, it updates the projectile’s state vector &lt;code&gt;[x, z, vx, vz]&lt;/code&gt;, considering drag, gravity, and velocity. The result is a trajectory array detailing the evolution of the projectile's position and velocity throughout its flight.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using Root-Finding (Bisection Method) to Hit a Specified Distance&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For a specified target distance, we need to determine the precise launch angle that will cause the projectile to land at the target. This is a root-finding problem: find the angle where &lt;code&gt;height_at_target(angle)&lt;/code&gt; equals ground level. The bisection method is preferred here: it’s robust, doesn’t require derivatives, and is simple to implement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start with low and high angle bounds.&lt;/li&gt;
&lt;li&gt;Iteratively bisect the interval, checking if the projectile overshoots or falls short at the target distance.&lt;/li&gt;
&lt;li&gt;Shrink the interval toward the angle whose trajectory lands closest to the desired point.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Numerical Interpolation for Accurate Landing Position&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even with fine time resolution, the discrete trajectory samples may bracket the exact target distance without matching it precisely. Simple linear interpolation between the two samples closest to the desired distance estimates the projectile’s true elevation at the target. This provides a continuous, high-accuracy solution without excessive oversampling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Practical Considerations: Numerical Stability and Accuracy vs. Speed&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stability:&lt;/strong&gt; Too large a time step risks instability (e.g., oscillating or diverging solutions). It's always wise to verify convergence by slightly varying sample count.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed vs. Accuracy:&lt;/strong&gt; Finer grids increase computational cost, but with tools like JAX and just-in-time compiling, you can afford higher resolution without significant slowdowns.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reproducibility:&lt;/strong&gt; Always document or fix the random seeds, simulation duration, and grid size for consistent results.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Example: Numerical Solution in Action&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s demonstrate these principles by implementing the full integration, root-finding, and interpolation steps for a simple projectile simulation. &lt;/p&gt;
&lt;p&gt;Here is the projectile's computed trajectory and the determined launch angle for a 500 m target:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tinycomputers.io/images/0bf49bf00-1223-4046-94b3-6577b3fe3797.png" style="width: 640px; box-shadow: 0 30px 40px rgba(0,0,0,.1); float: right; padding: 20px 20px 20px 20px;"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Analysis and Interpretation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Time grid and integration step:&lt;/strong&gt; The simulation used 2000 time samples over 4 seconds, achieving enough resolution to ensure accuracy without overloading computation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trajectory generation:&lt;/strong&gt; The ODE integrator (&lt;code&gt;odeint&lt;/code&gt;) produced an array representing the projectile's flight path, accounting for both gravity and drag at each instant.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Root-finding:&lt;/strong&gt; The bisection method iteratively determined the precise hold-over angle needed to strike the target. In this case, the solver found a solution of approximately 0.136 degrees.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Numerical interpolation:&lt;/strong&gt; To accurately determine where the projectile crosses the target distance, the height was linearly interpolated between the two closest trajectory points.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Practical tradeoff:&lt;/strong&gt; This workflow offers excellent reproducibility, efficient computation, and a reliable approach for balancing speed and accuracy. It can be easily adapted for parameter sweeps or “what-if” analyses in both ballistics and related domains.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Conclusion: The Power of JAX for Scientific Simulation&lt;/h3&gt;
&lt;p&gt;Over the course of this article, we walked through an end-to-end approach for simulating projectile motion using Python and modern computational techniques. We started by constructing the mathematical model, defining state vectors that track position and velocity while accounting for the effects of gravity and drag. By formulating the system as an ordinary differential equation (ODE), we created a robust foundation suitable for simulation, experimentation, and extension.&lt;/p&gt;
&lt;p&gt;We then discussed how to structure simulation code for clarity and extensibility, using configuration dictionaries for initial conditions and modular functions for dynamics and drag. The heart of the technical implementation leveraged JAX’s powerful features: just-in-time compilation (&lt;code&gt;@jit&lt;/code&gt;) and its high-performance, stateless &lt;code&gt;odeint&lt;/code&gt; integrator. This brings significant speed-ups, enables seamless experimentation through rapid parameter sweeps, and offers the added benefit of differentiability for optimization and machine learning applications.&lt;/p&gt;
&lt;p&gt;One of JAX’s greatest strengths is how it enables true exploratory numerical simulation. By harnessing hardware acceleration (CPU, GPU, TPU), researchers and engineers can quickly run many simulations, test out “what-if” questions, and iterate on their models, all from a single, flexible codebase. JAX’s functional purity ensures that results are reproducible and code remains maintainable, even as complexity increases.&lt;/p&gt;
&lt;p&gt;Looking ahead, this simulation framework can be further expanded in various directions:
- &lt;strong&gt;Batch simulations:&lt;/strong&gt; Run large sets of parameter combinations in parallel, enabling Monte Carlo analysis or uncertainty quantification.
- &lt;strong&gt;Stochastic effects:&lt;/strong&gt; Incorporate randomness (e.g., wind gusts, environmental fluctuation) for more realistic or robust predictions.
- &lt;strong&gt;Optimization:&lt;/strong&gt; Use automatic differentiation with JAX to tune system parameters for specific performance goals: maximizing range, minimizing dispersion, or matching experimental data.
- &lt;strong&gt;Higher dimensions:&lt;/strong&gt; Expand from 2D to full 3D trajectories or add additional physics (e.g., spin drift, Coriolis force).&lt;/p&gt;
&lt;p&gt;This modern, JAX-powered workflow not only accelerates traditional ballistics work but also positions researchers to innovate rapidly in research, engineering, and even interactive applications. The principles and techniques described here generalize to many fields whenever clear models, efficiency, and the freedom to explore “what if” truly matter.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="c1"&gt;# First, let's import JAX and related libraries.&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;jax.numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;jnp&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;jax&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jit&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;jax.experimental.ode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;odeint&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;plt&lt;/span&gt;

&lt;span class="c1"&gt;# CONFIGURATION&lt;/span&gt;
&lt;span class="n"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;'target_distance_m'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;500.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     
    &lt;span class="s1"&gt;'muzzle_height_m'&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      
    &lt;span class="s1"&gt;'muzzle_velocity_mps'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;920.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   
    &lt;span class="s1"&gt;'mass_kg'&lt;/span&gt;          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.00402&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   
    &lt;span class="s1"&gt;'diameter_m'&lt;/span&gt;       &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.00570&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   
    &lt;span class="s1"&gt;'air_density_kgpm3'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.225&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'gravity_mps2'&lt;/span&gt;     &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;9.80665&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'drag_family'&lt;/span&gt;      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'G1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'max_time_s'&lt;/span&gt;       &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'samples'&lt;/span&gt;          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Derived quantities&lt;/span&gt;
&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'gravity_mps2'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;rho_air&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'air_density_kgpm3'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'mass_kg'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'diameter_m'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;v0_muzzle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'muzzle_velocity_mps'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# G1 drag table (Mach → Cd)&lt;/span&gt;
&lt;span class="n"&gt;_g1_mach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mf"&gt;1.45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;1.90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;2.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;2.40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;2.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;2.80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mf"&gt;3.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;3.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;3.40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;3.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;3.80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;4.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;4.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;4.40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;4.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;4.80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;5.00&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;_g1_cd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="mf"&gt;0.127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.132&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.138&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.144&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.151&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.159&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.166&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.173&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.181&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.188&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.195&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mf"&gt;0.209&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.216&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.223&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.230&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.238&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.245&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.252&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.340&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.380&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.394&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mf"&gt;0.370&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.340&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.320&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.304&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.290&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.270&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.260&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.240&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.230&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.220&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mf"&gt;0.200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.195&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.185&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.170&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.165&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.155&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.147&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.144&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mf"&gt;0.141&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.138&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.135&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.132&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.130&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="nd"&gt;@jit&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;drag_cd_g1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;mach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;343.0&lt;/span&gt;
    &lt;span class="n"&gt;Cd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jnp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;interp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mach&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_g1_mach&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_g1_cd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_g1_cd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_g1_cd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Cd&lt;/span&gt;

&lt;span class="n"&gt;drag_cd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;drag_cd_g1&lt;/span&gt;

&lt;span class="c1"&gt;# ODE RHS&lt;/span&gt;
&lt;span class="nd"&gt;@jit&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="n"&gt;v_mag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jnp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vx&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;vz&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1e-9&lt;/span&gt;
    &lt;span class="n"&gt;Cd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;drag_cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v_mag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rho_air&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Cd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v_mag&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;ax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Fd&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vx&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;v_mag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;az&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Fd&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vz&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;v_mag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jnp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;az&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Shooting trajectory&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;shoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle_rad&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;vx0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v0_muzzle&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle_rad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;vz0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v0_muzzle&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle_rad&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;y0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'muzzle_height_m'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;vx0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vz0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;tgrid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'max_time_s'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'samples'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;traj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;odeint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tgrid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;traj&lt;/span&gt;

&lt;span class="c1"&gt;# Height at target function for bisection method&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;height_at_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;traj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;traj&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;traj&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;searchsorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'target_distance_m'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;1e3&lt;/span&gt;
    &lt;span class="n"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;z0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;z1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;z0&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;z0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'target_distance_m'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Find solution angle&lt;/span&gt;
&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deg2rad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deg2rad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;height_at_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;
&lt;span class="n"&gt;angle_solution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Launch angle needed (G1 drag): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rad2deg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle_solution&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.3f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;°"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Plot final trajectory&lt;/span&gt;
&lt;span class="n"&gt;traj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle_solution&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;traj&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;traj&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'target_distance_m'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Projectile trajectory'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axvline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'target_distance_m'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'gray'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'target_distance_m'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; m"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axhline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"5.56 mm (G1 drag) - hold-over &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rad2deg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle_solution&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;°"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Range (m)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Height (m)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tight_layout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;</description><category>auto-differentiation</category><category>ballistics</category><category>bisection method</category><category>code modularity</category><category>drag modeling</category><category>exploratory analysis</category><category>hardware acceleration</category><category>jax</category><category>just-in-time compilation</category><category>matplotlib</category><category>numerical integration</category><category>ode solver</category><category>parameter sweep</category><category>performance optimization</category><category>projectile simulation</category><category>python</category><category>root-finding</category><category>scientific computing</category><category>simulation</category><category>simulation visualization</category><category>trajectory analysis</category><category>trajectory plotting</category><guid>https://tinycomputers.io/posts/optimizing-scientific-simulations-jax-powered-ballistic-calculations.html</guid><pubDate>Thu, 15 May 2025 20:53:31 GMT</pubDate></item><item><title>Simulating Buckshot Spread – A Deep Dive with Python and ODEs</title><link>https://tinycomputers.io/posts/simulating-buckshot-spread-a-deep-dive-with-python-and-odes.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/simulating-buckshot-spread-a-deep-dive-with-python-and-odes_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;25 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Shotguns are celebrated for their unique ability to launch a cluster of small projectiles (referred to as pellets) simultaneously, making them highly effective at short ranges in hunting, sport shooting, and defensive scenarios. The way these pellets separate and spread apart during flight creates the signature pattern seen on shotgun targets. While the general term “shot” applies to all such projectiles, specific pellet sizes exist, each with distinct ballistic properties. In this article, we will focus on modeling &lt;a href="https://go.tinymachines.io/7LXs8E"&gt;#00 buckshot&lt;/a&gt;, a popular choice for both self-defense and law enforcement applications due to its larger pellet size and stopping power.&lt;/p&gt;
&lt;p&gt;By using Python, we’ll construct a simulation that predicts the paths and spread of #00 buckshot pellets after they leave the barrel. Drawing from principles of physics (like gravity and aerodynamic drag) and incorporating randomness to reflect real-world variation, our code will numerically solve each pellet’s flight path. This approach lets us visualize the resulting shot pattern at a chosen distance downrange and gain a deeper appreciation for how ballistic forces and initial conditions shape what happens when the trigger is pulled.&lt;/p&gt;
&lt;h3&gt;Understanding the Physics of Shotgun Pellets&lt;/h3&gt;
&lt;p&gt;When a shotgun is fired, each pellet exits the barrel at a significant velocity, starting a brief yet complex flight through the air. The physical forces acting on the pellets dictate their individual paths and, ultimately, the characteristic spread pattern observed at the target. To create an accurate simulation of this process, it’s important to understand the primary factors influencing pellet motion.&lt;/p&gt;
&lt;p&gt;The most fundamental force is gravity. This constant downward pull, at approximately 9.81 meters per second squared, causes pellets to fall toward the earth as they travel forward. The effect of gravity is immediate: even with a rapid muzzle velocity, pellets begin to drop soon after leaving the barrel, and this drop becomes more noticeable over longer distances.&lt;/p&gt;
&lt;p&gt;Another critical factor, particularly relevant for small and light projectiles such as #00 buckshot, is aerodynamic drag. As a pellet speeds through the air, it constantly encounters resistance from air molecules in its path. Drag not only oppose the pellet’s motion but also increases rapidly with speed; it is proportional to the square of the velocity. The magnitude of this force depends on properties such as the pellet’s cross-sectional area, mass, and shape (summarized by the drag coefficient). In this model, we assume all pellets are nearly spherical and share the same mass and size, using standard values for drag.&lt;/p&gt;
&lt;p&gt;The interplay between gravity and aerodynamic drag controls how far each pellet travels and how much it slows before reaching the target. These forces are at the core of external ballistics, shaping how the tight column of pellets at the muzzle becomes a broad pattern by the time it arrives downrange. Understanding and accurately representing these effects is essential for any simulation that aims to realistically capture shotgun pellet motion.&lt;/p&gt;
&lt;h3&gt;Setting Up the Simulation&lt;/h3&gt;
&lt;p&gt;Before simulating shotgun pellet flight, the foundation of the model must be established through a series of physical parameters. These values are crucial; they dictate everything from the amount of drag experienced by a pellet to the degree of possible spread observed on a target.&lt;/p&gt;
&lt;p&gt;First, the code defines characteristics of a single #00 buckshot pellet. The pellet diameter (&lt;code&gt;d&lt;/code&gt;) is set to 0.0084 meters, giving a radius (&lt;code&gt;r&lt;/code&gt;) of half that value. The cross-sectional area (&lt;code&gt;A&lt;/code&gt;) is calculated as π times the radius squared. This area directly impacts how much air resistance the pellet experiences; the larger the cross-section, the more drag slows it down. The mass (&lt;code&gt;m&lt;/code&gt;) is set to 0.00351 kilograms, representing the weight of an individual #00 pellet in a standard shotgun load.&lt;/p&gt;
&lt;p&gt;Next, the code specifies values needed for the calculation of aerodynamic drag. The drag coefficient (&lt;code&gt;Cd&lt;/code&gt;) is set to 0.47, a typical value for a sphere moving through air. Air density (&lt;code&gt;rho&lt;/code&gt;) is specified as 1.225 kilograms per cubic meter, which is a standard value at sea level under average conditions, and gravity (&lt;code&gt;g&lt;/code&gt;) is established as 9.81 meters per second squared.&lt;/p&gt;
&lt;p&gt;The number of pellets to simulate is set with &lt;code&gt;num_pellets&lt;/code&gt;; here, nine pellets are used, reflecting a common #00 buckshot shell configuration. The &lt;code&gt;v0&lt;/code&gt; parameter sets the initial (muzzle) velocity for each pellet, at 370 meters per second, a realistic value for modern 12-gauge loads. To add realism, slight random variation in velocity is included using &lt;code&gt;v_sigma&lt;/code&gt;, which allows muzzle velocity to be sampled from a normal distribution for each pellet. This captures the real-world variability inherent in a shotgun shot.&lt;/p&gt;
&lt;p&gt;To model the spread of pellets as they leave the barrel, the code uses &lt;code&gt;spread_std_deg&lt;/code&gt; and &lt;code&gt;spread_max_deg&lt;/code&gt;. These parameters define the standard deviation and maximum value for the random angular deviation of each pellet in both horizontal and vertical directions. This gives each pellet a unique initial direction, simulating the inherent randomness and choke effect seen in actual shotgun blasts.&lt;/p&gt;
&lt;p&gt;Initial position coordinates (&lt;code&gt;x0&lt;/code&gt;, &lt;code&gt;y0&lt;/code&gt;, &lt;code&gt;z0&lt;/code&gt;) establish where the pellets start: here, at the muzzle, with the barrel one meter off the ground. The &lt;code&gt;pattern_distance&lt;/code&gt; defines how far away the “target” is placed, setting the plane where pellet impacts are measured. Finally, &lt;code&gt;max_time&lt;/code&gt; sets a hard cap on the simulated flight duration, ensuring computations finish even if a pellet never hits the ground or target.&lt;/p&gt;
&lt;p&gt;By specifying all these parameters before running the simulation, the code grounds its calculations in real-world physical properties, establishing a robust and realistic baseline for the ODE-based modeling that follows.&lt;/p&gt;
&lt;h3&gt;The ODE Model&lt;/h3&gt;
&lt;p&gt;At the heart of the simulation is a mathematical model that describes each pellet’s motion using an &lt;a href="https://go.tinymachines.io/qnDVRv"&gt;ordinary differential equation&lt;/a&gt; (ODE). The state of a pellet in flight is captured by six variables: its position in three dimensions (x, y, z) and its velocity in each direction (vx, vy, vz). As the pellet travels, both gravity and aerodynamic drag act on it, continually altering its velocity and trajectory.&lt;/p&gt;
&lt;p&gt;Gravity is straightforward in the model: a constant downward acceleration, reducing the y-component (height) of the pellet’s velocity over time. The trickier part is aerodynamic drag, which opposes the pellet’s motion and depends on both its speed and orientation. In this simulation, drag is modeled using the standard quadratic law, which states that the decelerating force is proportional to the square of the velocity. Mathematically, the drag acceleration in each direction is calculated as:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;dv/dt = -k &lt;span class="gs"&gt;* v *&lt;/span&gt; v_dir
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;where &lt;code&gt;k&lt;/code&gt; bundles together the effects of drag coefficient, air density, area, and mass, &lt;code&gt;v&lt;/code&gt; is the current speed, and &lt;code&gt;v_dir&lt;/code&gt; is a velocity component (vx, vy, or vz).&lt;/p&gt;
&lt;p&gt;Within the &lt;code&gt;pellet_ode&lt;/code&gt; function, the code computes the combined velocity from its three components and then applies this drag to each directional velocity. Gravity appears as a constant subtraction from the vertical (vy) acceleration. The ODE function returns the derivatives of all six state variables, which are then numerically integrated over time using Scipy’s &lt;code&gt;solve_ivp&lt;/code&gt; routine.&lt;/p&gt;
&lt;p&gt;By combining these physics-based rules, the ODE produces realistic pellet flight paths, showing how each is steadily slowed by drag and pulled downward by gravity on its journey from muzzle to target.&lt;/p&gt;
&lt;h3&gt;Modeling Pellet Spread: Incorporating Randomness&lt;/h3&gt;
&lt;p&gt;A defining feature of shotgun use is the spread of pellets as they exit the barrel and travel toward the target. While the physics of flight create predictable paths, the divergence of each pellet from the bore axis is largely random, influenced by manufacturing tolerances, barrel choke, and small perturbations at ignition. To replicate this in simulation, the code incorporates controlled randomness into the initial direction and velocity of each pellet.&lt;/p&gt;
&lt;p&gt;For every simulated pellet, two angles are generated: one for vertical (up-down) deviation and one for horizontal (left-right) deviation. These angles are drawn from a normal (Gaussian) distribution centered at zero, reflecting the natural scatter expected from a well-maintained shotgun. Standard deviation and maximum values (set by &lt;code&gt;spread_std_deg&lt;/code&gt; and &lt;code&gt;spread_max_deg&lt;/code&gt;) control the tightness and outer limits of this spread. This ensures realistic variation while preventing extreme outliers not seen in practice.&lt;/p&gt;
&lt;p&gt;Muzzle velocity is also subject to small random variation. While the manufacturer’s rating might place velocity at 370 meters per second, factors like ammunition inconsistencies and environmental conditions can introduce fluctuations. By sampling the initial velocity for each pellet from a normal distribution (with mean &lt;code&gt;v0&lt;/code&gt; and standard deviation &lt;code&gt;v_sigma&lt;/code&gt;), the simulator reproduces this subtle randomness.&lt;/p&gt;
&lt;p&gt;To determine starting velocities in three dimensions (vx, vy, vz), the code applies trigonometric calculations based on the sampled initial angles and speed, ensuring that each pellet’s departure vector deviates uniquely from the barrel’s axis. The result is a spread pattern that closely mirrors those seen in field tests: a dense central cluster with some pellets landing closer to the edge.&lt;/p&gt;
&lt;p&gt;By weaving calculated randomness into the simulation’s initial conditions, the code not only matches the unpredictable nature of real-world shot patterns, but also creates meaningful output for analyzing shotgun effectiveness and pattern density at various distances.&lt;/p&gt;
&lt;h3&gt;ODE Integration with Boundary Events&lt;/h3&gt;
&lt;p&gt;Simulating the trajectory of each pellet requires numerically solving the equations of motion over time. This is accomplished by passing the ODE model to SciPy’s &lt;code&gt;solve_ivp&lt;/code&gt; function, which integrates the system from the pellet’s moment of exit until it either hits the ground, the target plane, or a maximum time is reached. To handle these criteria efficiently, the code employs two “event” functions that monitor for specific conditions during integration.&lt;/p&gt;
&lt;p&gt;The first event, &lt;code&gt;ground_event&lt;/code&gt;, is triggered when a pellet’s vertical position (&lt;code&gt;y&lt;/code&gt;) reaches zero, corresponding to ground impact. This event is marked as terminal in the integration, so once triggered, the ODE solver halts further calculation for that pellet, ensuring we don’t simulate motion beneath the earth.&lt;/p&gt;
&lt;p&gt;The second event, &lt;code&gt;pattern_event&lt;/code&gt;, fires when the pellet’s downrange distance (&lt;code&gt;x&lt;/code&gt;) equals the designated pattern distance. This captures the precise moment a pellet crosses the plane of interest, such as a target board at 5 meters. Unlike &lt;code&gt;ground_event&lt;/code&gt;, this event is not terminal, allowing the solver to keep tracking the pellet in case it flies beyond the target distance before landing.&lt;/p&gt;
&lt;p&gt;By combining these event-driven stops with dense output (for smooth interpolation) and a small integration step size, the code accurately and efficiently identifies either the ground impact or the target crossing for each pellet. This strategy ensures that every significant outcome in the flight, whether a hit or a miss, is reliably captured in the simulation.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scipy.integrate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;solve_ivp&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;plt&lt;/span&gt;

&lt;span class="c1"&gt;# Physical constants&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0084&lt;/span&gt;      &lt;span class="c1"&gt;# m&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# m^2&lt;/span&gt;
&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.00351&lt;/span&gt;     &lt;span class="c1"&gt;# kg&lt;/span&gt;
&lt;span class="n"&gt;Cd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.47&lt;/span&gt;
&lt;span class="n"&gt;rho&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.225&lt;/span&gt;     &lt;span class="c1"&gt;# kg/m^3&lt;/span&gt;
&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;9.81&lt;/span&gt;        &lt;span class="c1"&gt;# m/s^2&lt;/span&gt;

&lt;span class="n"&gt;num_pellets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;
&lt;span class="n"&gt;v0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;370&lt;/span&gt;        &lt;span class="c1"&gt;# muzzle velocity m/s&lt;/span&gt;
&lt;span class="n"&gt;v_sigma&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="n"&gt;spread_std_deg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;
&lt;span class="n"&gt;spread_max_deg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;

&lt;span class="n"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.&lt;/span&gt;

&lt;span class="n"&gt;pattern_distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;    &lt;span class="c1"&gt;# m&lt;/span&gt;
&lt;span class="n"&gt;max_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pellet_ode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vx&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;vy&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;vz&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Cd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rho&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
    &lt;span class="n"&gt;dxdt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vx&lt;/span&gt;
    &lt;span class="n"&gt;dydt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vy&lt;/span&gt;
    &lt;span class="n"&gt;dzdt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vz&lt;/span&gt;
    &lt;span class="n"&gt;dvxdt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;vx&lt;/span&gt;
    &lt;span class="n"&gt;dvydt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;
    &lt;span class="n"&gt;dvzdt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;vz&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dxdt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dydt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dzdt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dvxdt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dvydt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dvzdt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;pattern_z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;pattern_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="n"&gt;trajectories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_pellets&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Randomize initial direction for spread&lt;/span&gt;
    &lt;span class="n"&gt;theta_h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;radians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spread_std_deg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;theta_h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta_h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;radians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spread_max_deg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;radians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spread_max_deg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;theta_v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;radians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spread_std_deg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;theta_v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta_v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;radians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spread_max_deg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;radians&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spread_max_deg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;v0p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v_sigma&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Forward is X axis. Up is Y axis. Left-right is Z axis&lt;/span&gt;
    &lt;span class="n"&gt;vx0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v0p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta_v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta_h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;vy0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v0p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta_v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;vz0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v0p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta_v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta_h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;ic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vx0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vy0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vz0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ground_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# y[1] is height&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;ground_event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;terminal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
    &lt;span class="n"&gt;ground_event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pattern_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;   &lt;span class="c1"&gt;# y[0] is x&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;pattern_distance&lt;/span&gt;
    &lt;span class="n"&gt;pattern_event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;terminal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;pattern_event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="n"&gt;sol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solve_ivp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;pellet_ode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_time&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;ic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ground_event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern_event&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;dense_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_step&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Find the stopping time: whichever is first, ground or simulation end&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t_events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;t_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t_events&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;t_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;t_plot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;trajectories&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t_plot&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Interpolate to pattern_distance for hit pattern&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern_distance&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern_distance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# avoid index out of bounds if already starting beyond pattern_distance&lt;/span&gt;
            &lt;span class="n"&gt;frac&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern_distance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;zhit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;frac&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;yhit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;frac&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;yhit&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;pattern_z&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zhit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;pattern_y&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yhit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# --- Plot 3D trajectories ---&lt;/span&gt;
&lt;span class="n"&gt;fig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_subplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;111&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;projection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'3d'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;traj&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;trajectories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;traj&lt;/span&gt;
    &lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Downrange X (m)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Left-Right Z (m)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_zlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Height Y (m)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'3D Buckshot Pellet Trajectories (ODE solver)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# --- Plot pattern on 25m target plane ---&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;circle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Circle&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.2032&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'b'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;linestyle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'--'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'8 inch target'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gca&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern_z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern_y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Pellet hits'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Left-Right Offset (m)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;'Height (m), target at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pattern_distance&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; m'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;'Buckshot Pattern at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pattern_distance&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; m'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axhline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Muzzle height'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axvline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ylim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gca&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_aspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'equal'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adjustable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'box'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Recording and Visualizing Pellet Impacts&lt;/h3&gt;
&lt;p&gt;Once a pellet’s trajectory has been simulated, it is important to determine exactly where it would strike the target plane placed at the specified downrange distance. Because the pellet’s position is updated in discrete time steps, it rarely lands exactly at the &lt;code&gt;pattern_distance&lt;/code&gt;. Therefore, the code detects when the pellet’s simulated x-position first passes this distance. At this point, a linear interpolation is performed between the two positions bracketing the target plane, calculating the precise y (height) and z (left-right) coordinates where the pellet would intersect the pattern distance. This ensures consistent and accurate hit placement regardless of integration step size.&lt;/p&gt;
&lt;p&gt;The resulting values for each pellet are appended to the &lt;code&gt;pattern_y&lt;/code&gt; and &lt;code&gt;pattern_z&lt;/code&gt; lists. These lists collectively represent the full group of pellet impact points at the target plane and can be conveniently visualized or analyzed further.&lt;/p&gt;
&lt;p&gt;By recording these interpolated impact points, the simulation offers direct insight into the spatial distribution of pellets on the target. This data allows shooters and engineers to assess key real-world characteristics such as pattern density, evenness, and the likelihood of hitting a given area. In visualization, these points paint a clear picture of spread and clustering, helping to understand both shotgun effectiveness and pellet behavior under the influence of drag and gravity.&lt;/p&gt;
&lt;h3&gt;Visualization: Plotting Trajectories and Impact Patterns&lt;/h3&gt;
&lt;p&gt;Visualizing the results of the simulation offers both an intuitive understanding of pellet motion and practical insight into shotgun performance. The code provides two types of plots: a three-dimensional trajectory plot and a two-dimensional pattern plot on the target plane.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tinycomputers.io/images/downrange.png" style="width: 640px; box-shadow: 0 30px 40px rgba(0,0,0,.1); float: left; padding: 20px 20px 20px 20px;"&gt;The 3D trajectory plot displays the full flight paths of all simulated pellets, with axes labeled for downrange distance (&lt;code&gt;x&lt;/code&gt;), left-right offset (&lt;code&gt;z&lt;/code&gt;), and vertical height (&lt;code&gt;y&lt;/code&gt;). Each pellet's arc is traced from muzzle exit to endpoint, revealing not just forward travel and fall due to gravity, but also the sideways spread caused by angular deviation and drag. This plot gives a comprehensive, real-time sense of how pellets diverge and lose height, much like visualizing the flight of shot in slow motion. It can highlight trends such as gradual drop-offs, the effect of random spread angles, and which pellets remain above the ground longest.&lt;/p&gt;
&lt;p&gt;The pattern plane plot focuses on practical outcomes: the locations where pellets would strike a target at a given distance (e.g., 5 meters downrange). An 8-inch circle is superimposed to represent a common target size, providing context for real-world shooting scenarios. Each simulated impact point is marked, showing the actual distribution and clustering of pellets. Reference lines denote the muzzle height (horizontal) and the barrel center (vertical), helping to orient the viewer and relate simulated results to how a shooter would aim.&lt;/p&gt;
&lt;p&gt;Together, these visuals bridge the gap between abstract trajectory calculations and real shooting experience. The 3D plot helps explore external ballistics, while the pattern plot reflects what a shooter would see on a paper target at the range, key information for understanding spread, pattern density, and shotgun effectiveness.&lt;/p&gt;
&lt;h3&gt;Assumptions &amp;amp; Limitations of the Model&lt;/h3&gt;
&lt;p&gt;While this simulation offers a physically grounded view of #00 buckshot spread, several simplifying assumptions shape its results. The code treats all pellets as perfectly spherical, identical in size and mass, and does not account for pellet deformation or fracturing, both of which can occur during firing or impact. Air properties are held constant, with fixed density and drag coefficient values; in reality, both can change due to weather, altitude, and even fluctuations in pellet speed.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tinycomputers.io/images/buckshot-spread.png" style="width: 340px; box-shadow: 0 30px 40px rgba(0,0,0,.1); float: right; padding: 20px 20px 20px 20px;"&gt;The external environment in the model is idealized: there is no simulated wind, nor do pellets interact with one another mid-flight. Real pellets may collide or influence each other's paths, especially immediately after leaving the barrel. The simulation also omits nuanced effects of shotgun choke or barrel design, instead representing spread as a simple random angle without structure, patterning, or environmental response. The shooter’s aim is assumed perfectly flat, originating from a set muzzle height, with no allowance for human error or tilt.&lt;/p&gt;
&lt;p&gt;These simplifications mean that actual shotgun patterns may differ in meaningful ways. Real-world patterns can display uneven density, elliptical shapes from chokes, or wind-induced drift, all absent from this model. Furthermore, pellet deformation can lead to less predictable spread, and varying air conditions or shooter input can add additional variability. Nevertheless, the simulation provides a valuable baseline for understanding the primary forces and expected outcomes, even if it cannot capture every subtlety from live fire.&lt;/p&gt;
&lt;h3&gt;Possible Improvements and Extensions&lt;/h3&gt;
&lt;p&gt;This simulation, while useful for visualizing basic pellet dynamics, could be made more realistic by addressing some of its idealizations. Incorporating wind modeling would add lateral drift, making the simulation more applicable to outdoor shooting scenarios. Simulating non-spherical or deformed pellets (accounting for variations in shape, mass, or surface) could change each pellet’s drag and produce more irregular spread patterns. Introducing explicit choke effects would allow for non-uniform or elliptical spreads that better match the output from different shotgun barrels and constrictions.&lt;/p&gt;
&lt;p&gt;Environmental factors like altitude and temperature could be included to adjust air density and drag coefficient dynamically, reflecting their real influence on ballistics. Finally, modeling shooter-related factors such as sight alignment, aim variation, or recoil-induced muzzle movement would add further variability. Collectively, these enhancements would move the simulation closer to the unpredictable reality of shotgun use, providing even greater value for shooters, ballistics researchers, and enthusiasts alike.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Physically-accurate simulations of shotgun pellet spread offer valuable lessons for both programmers and shooting enthusiasts. By translating real-world ballistics into code, we gain a deeper understanding of the factors that shape shot patterns and how subtle changes in variables can influence outcomes. Python, paired with SciPy’s ODE solvers, proves to be an accessible and powerful toolkit for exploring these complex systems. Whether used for educational insight, hobby experimentation, or designing safer and more effective ammunition, this approach opens the door to further exploration. Readers are encouraged to adapt, extend, or refine the code to match their own interests and scenarios.&lt;/p&gt;
&lt;h3&gt;References &amp;amp; Further Reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://baud.rs/tboAIk"&gt;McCoy, R.L., &lt;em&gt;Modern Exterior Ballistics&lt;/em&gt;&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.tinymachines.io/qB9nHt"&gt;L.P. Brezny, &lt;em&gt;Gun Digest Book of Shotgunning&lt;/em&gt;&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;Python/Scipy ODE Integrators: &lt;a href="https://go.tinymachines.io/9T9cnZ"&gt;scipy.integrate.solve_ivp&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.tinymachines.io/bbydTU"&gt;Chuck Hawks’ Shotgun Ballistics Resource&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.tinymachines.io/5P3UFT"&gt;Ballistics Science (Wikipedia)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category>#00 buckshot</category><category>ammunition</category><category>ballistics</category><category>ballistics simulation</category><category>code walkthrough</category><category>computational modeling</category><category>drag</category><category>external ballistics</category><category>external forces</category><category>gravity</category><category>matplotlib</category><category>muzzle velocity</category><category>numpy</category><category>ode solver</category><category>pellet spread</category><category>pellet trajectory</category><category>physics</category><category>programming</category><category>projectile motion</category><category>python</category><category>randomness</category><category>scientific computing</category><category>scipy</category><category>shot pattern</category><category>shotgun</category><category>shotgun choke</category><category>simulation</category><category>target pattern</category><category>visualization</category><category>wind modeling</category><guid>https://tinycomputers.io/posts/simulating-buckshot-spread-a-deep-dive-with-python-and-odes.html</guid><pubDate>Fri, 09 May 2025 00:12:22 GMT</pubDate></item><item><title>Modeling Recoil Dynamics</title><link>https://tinycomputers.io/posts/modeling-recoil-dynamics.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/modeling-recoil-dynamics_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;22 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;Modeling the Recoil Dynamics of the AR-15 Rifle using Python and Differential Equations&lt;/h2&gt;
&lt;p&gt;When analyzing the performance of the AR-15 rifle or any firearm, understanding the recoil characteristics is essential. Recoil impacts the ability of shooters to control the rifle, quickly re-acquire targets, and maintain accuracy. In this post, we'll walk through the mathematics and physics behind recoil dynamics and demonstrate how to numerically simulate it using Python.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Understanding Firearm Recoil: A Mechanical Perspective&lt;/h3&gt;
&lt;h4&gt;The Physics of Recoil&lt;/h4&gt;
&lt;p&gt;Recoil occurs from &lt;a href="https://go.tinymachines.io/p9MEQ2"&gt;conservation of momentum&lt;/a&gt;: as the bullet and gases accelerate forward through the barrel, the rifle pushes backward against the shooter. To realistically simulate this, we consider the entire shooter-rifle system as a mechanical structure composed of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mass (M)&lt;/strong&gt;: Effective mass of the rifle and shooter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Damping (C)&lt;/strong&gt;: Dissipative force (body tissues, rifle buttstock pad).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring (k)&lt;/strong&gt;: The internal buffer spring of the rifle itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;a href="https://go.tinymachines.io/uPtNxZ"&gt;Modeling as a Mass-Spring-Damper System&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;We can express this physically as a &lt;a href="https://go.tinymachines.io/qQ7N8L"&gt;second-order&lt;/a&gt; &lt;a href="https://go.tinymachines.io/WEvPkn"&gt;ordinary differential equation&lt;/a&gt; (ODE):&lt;/p&gt;
&lt;p&gt;$$
M\frac{d^2x}{dt^2} + C\frac{dx}{dt} + kx = -F_{\text{recoil}}(t)
$$&lt;/p&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$(x(t))$: Rearward displacement.&lt;/li&gt;
&lt;li&gt;$(M)$: Mass of shooter + rifle.&lt;/li&gt;
&lt;li&gt;$(C)$: Damping coefficient (shoulder and body dampening effect).&lt;/li&gt;
&lt;li&gt;$(k)$: Internal rifle-buffer spring stiffness.&lt;/li&gt;
&lt;li&gt;$(F_{\text{recoil}})$: Force during bullet acceleration (short impulse duration).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Converting the Recoil ODE into a Numerical Form&lt;/h4&gt;
&lt;p&gt;To numerically solve the recoil equation, we'll rewrite it as a set of two &lt;a href="https://go.tinymachines.io/AgPaaR"&gt;first-order equations&lt;/a&gt;. Let:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$(x_1 = x)$&lt;/li&gt;
&lt;li&gt;$(x_2 = \frac{dx}{dt})$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;p&gt;$$
\frac{dx_1}{dt} = x_2 \
\frac{dx_2}{dt} = \frac{-C x_2 - k x_1 -F_{\text{recoil}}(t)}{M}
$$&lt;/p&gt;
&lt;h4&gt;Recoil Force and Impulse Duration&lt;/h4&gt;
&lt;p&gt;For the AR-15, recoil force $(F_{\text{recoil}})$ exists briefly while the bullet accelerates. The force can be approximated as the momentum change per impulse duration, i.e.,&lt;/p&gt;
&lt;p&gt;$$
F_{\text{recoil}} = \frac{m_{\text{bullet}} v_{\text{bullet}}}{t_{\text{impulse}}}
$$&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Simulating AR-15 Recoil with Python&lt;/h3&gt;
&lt;p&gt;Now let's solve the equations numerically. Below are typical AR-15 parameters we will use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rifle &amp;amp; Shooter mass, $(M\ ≈ 5\ \text{kg})$&lt;/li&gt;
&lt;li&gt;Bullet mass, $(m_{bullet}\ ≈ 4\ grams\ ≈ 0.004\ kg)$&lt;/li&gt;
&lt;li&gt;Muzzle velocity, $(v_{bullet}\ ≈ 975\ \text{m/s})$&lt;/li&gt;
&lt;li&gt;Impulse duration, $(t_{\text{impulse}}\ ≈\ 0.5\ ms\ =\ 0.0005\ s)$&lt;/li&gt;
&lt;li&gt;Damping coefficient, $(C ≈ 150\ Ns/m)$ (estimated)&lt;/li&gt;
&lt;li&gt;Internal buffer Spring constant, $(k ≈ 10000\ N/m)$&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Python Code Implementation:&lt;/h4&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scipy.integrate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;solve_ivp&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;plt&lt;/span&gt;

&lt;span class="c1"&gt;# AR-15 Parameters&lt;/span&gt;
&lt;span class="n"&gt;m_bullet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.004&lt;/span&gt;              &lt;span class="c1"&gt;# Bullet mass [kg]&lt;/span&gt;
&lt;span class="n"&gt;v_bullet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;975&lt;/span&gt;                &lt;span class="c1"&gt;# Bullet velocity [m/s]&lt;/span&gt;
&lt;span class="n"&gt;M&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;                       &lt;span class="c1"&gt;# Effective recoil mass [kg] (rifle + shooter)&lt;/span&gt;
&lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;                       &lt;span class="c1"&gt;# Damping coefficient [N·s/m]&lt;/span&gt;
&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;                     &lt;span class="c1"&gt;# Buffer spring constant [N/m]&lt;/span&gt;
&lt;span class="n"&gt;t_impulse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0005&lt;/span&gt;            &lt;span class="c1"&gt;# Recoil impulse duration [s]&lt;/span&gt;

&lt;span class="c1"&gt;# Compute recoil force&lt;/span&gt;
&lt;span class="n"&gt;F_recoil&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m_bullet&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v_bullet&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;t_impulse&lt;/span&gt;

&lt;span class="c1"&gt;# Define system of equations&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;recoil_system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;t_impulse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;F_recoil&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;dxdt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
    &lt;span class="n"&gt;dvdt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dxdt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dvdt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Initial conditions — rifle initially at rest&lt;/span&gt;
&lt;span class="n"&gt;init_conditions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Time span (0 to 100 ms simulation)&lt;/span&gt;
&lt;span class="n"&gt;t_span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;t_eval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Numerical Solution&lt;/span&gt;
&lt;span class="n"&gt;sol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solve_ivp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recoil_system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t_span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;init_conditions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t_eval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;t_eval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Results Extraction&lt;/span&gt;
&lt;span class="n"&gt;displacement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="c1"&gt;# convert to mm&lt;/span&gt;
&lt;span class="n"&gt;velocity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;            &lt;span class="c1"&gt;# m/s&lt;/span&gt;

&lt;span class="c1"&gt;# Plot Results&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Displacement Plot&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;211&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;displacement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Displacement (mm)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axvline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t_impulse&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;linestyle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Impulse End"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"AR-15 Recoil Displacement with Buffer Spring"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Time [ms]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Displacement [mm]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Velocity Plot&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;212&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;velocity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Velocity (m/s)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"orange"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axvline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t_impulse&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;linestyle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Impulse End"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"AR-15 Recoil Velocity with Buffer Spring"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Time [ms]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Velocity [m/s]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tight_layout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;hr&gt;
&lt;div style="text-align: center;"&gt;
&lt;img src="https://tinycomputers.io/images/modeling-recoil-spring.png" style="width: 100%; box-shadow: 0 30px 40px rgba(0,0,0,.1); padding: 20px 20px 20px 20px;"&gt;
&lt;/div&gt;

&lt;h3&gt;Results: Analyzing the AR-15 Recoil Behavior&lt;/h3&gt;
&lt;p&gt;In the plots above, the model provides insightful details about how recoil unfolds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rapid initial impulse:&lt;/strong&gt; The rifle moves backward sharply in the initial half-millisecond recoil impulse stage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Buffer spring action:&lt;/strong&gt; Quickly after the impulse period, the buffer spring engages, significantly reducing velocity and displacement.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Damping &amp;amp; Recoil absorption:&lt;/strong&gt; After the initial sharp step, damping forces take effect, dissipating recoil energy, slowing and gradually stopping rifle movement.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This recoil model aligns with how AR-15 rifles mechanically operate in reality: A buffer spring significantly assists in absorbing and dissipating recoil, enhancing accuracy, comfort, and controllability.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Improving the Model Further&lt;/h3&gt;
&lt;p&gt;While the current simplified model closely reflects reality, future extensions can be considered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nonlinear damping:&lt;/strong&gt; A real shooter's body provides nonlinear damping. Implementing this would enhance realism.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Internal Gas Dynamics:&lt;/strong&gt; Real recoil also involves expanding gases. A more sophisticated model might consider gas forces explicitly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shooter's biomechanics:&lt;/strong&gt; Accurately modeling human biomechanics as nonlinear springs and dampers could yield even more realistic scenarios.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Through numerical simulation with Python and differential equation modeling, we've created an insightful approach to understanding firearm dynamics, in this case the AR-15 platform. Such understanding aids in rifle design improvements, shooter ergonomics, and analysis of recoil management accessories.&lt;/p&gt;
&lt;p&gt;Firearm analysts, engineers, and enthusiasts can adapt this flexible simulation method for other firearms, systems with differing parameters, or even entirely different recoil mechanisms.&lt;/p&gt;
&lt;p&gt;Through computational modeling like this, the physics underlying firearm recoil becomes uniquely visual and intuitive, enhancing engineering, training, and technique evaluation alike.&lt;/p&gt;</description><category>ar-15</category><category>buffer spring</category><category>computational physics</category><category>differential equations</category><category>firearm physics</category><category>firearms engineering</category><category>mass-spring-damper system</category><category>matplotlib</category><category>numerical modeling</category><category>python simulation</category><category>recoil dynamics</category><category>recoil modeling</category><category>scipy</category><category>shooting ergonomics</category><guid>https://tinycomputers.io/posts/modeling-recoil-dynamics.html</guid><pubDate>Sat, 05 Apr 2025 02:08:11 GMT</pubDate></item></channel></rss>