import streamlit as st import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation import streamlit.components.v1 as components # =============================== # Streamlit Interface Setup # =============================== st.title("Spin Launch & Orbital Attachment Simulation (Animated)") st.markdown( """ This simulation demonstrates a two‑phase launch: 1. **Spin‑Launch Phase:** A payload is accelerated from an elevated platform. 2. **Orbital Phase:** At a specified altitude, the payload “docks” with a human‑rated upper stage by instantly setting its velocity to that of a circular orbit. Adjust the parameters on the sidebar and click **Run Simulation**. """ ) # Sidebar parameters for simulation: st.sidebar.header("Simulation Parameters") # Time and integration parameters: dt = st.sidebar.number_input("Time Step (s)", min_value=0.01, max_value=1.0, value=0.1, step=0.01) t_max = st.sidebar.number_input("Total Simulation Time (s)", min_value=100, max_value=5000, value=1000, step=100) # Launch conditions: initial_altitude_km = st.sidebar.number_input("Initial Altitude (km)", min_value=1, max_value=100, value=50, step=1) initial_velocity = st.sidebar.number_input("Initial Velocity (m/s)", min_value=100, max_value=5000, value=1200, step=100) # Docking/Orbital attachment altitude: docking_altitude_km = st.sidebar.number_input("Docking Altitude (km)", min_value=1, max_value=500, value=100, step=1) # Button to run simulation: run_sim = st.sidebar.button("Run Simulation") if run_sim: st.info("Running simulation... Please wait.") # =============================== # Physical Constants and Conversions # =============================== mu = 3.986e14 # Earth's gravitational parameter, m^3/s^2 R_E = 6.371e6 # Earth's radius, m # Convert altitude values from km to m: initial_altitude = initial_altitude_km * 1000.0 # initial altitude above Earth's surface (m) docking_altitude = docking_altitude_km * 1000.0 # docking altitude above Earth's surface (m) r_dock = R_E + docking_altitude # docking radius from Earth's center (m) # =============================== # Initial Conditions for the Simulation # =============================== # Starting at a point along the x-axis at a radial distance (Earth's radius + initial altitude) initial_position = np.array([R_E + initial_altitude, 0.0]) # Initial velocity is chosen to be radial (pointing outward) at the chosen speed. initial_velocity_vec = np.array([initial_velocity, 0.0]) # =============================== # Define the Acceleration Function # =============================== def acceleration(pos): """Compute acceleration due to Earth's gravity at position pos (in m).""" r = np.linalg.norm(pos) return -mu * pos / r**3 # =============================== # Run the Simulation Loop # =============================== # We'll record (time, x, y, phase) # phase = 1: spin‑launch phase # phase = 2: orbital phase (after docking) states = [] phase = 1 t = 0.0 pos = initial_position.copy() vel = initial_velocity_vec.copy() docking_done = False docking_event_time = None docking_event_coords = None while t < t_max: # --- Check for the Docking Event --- # When the payload's distance from Earth's center exceeds the docking radius # and it's still moving outward, trigger the docking event. if phase == 1 and not docking_done and np.linalg.norm(pos) >= r_dock and vel.dot(pos) > 0: docking_done = True phase = 2 # switch to orbital phase r_current = np.linalg.norm(pos) # Compute the circular orbital speed at the current radius: v_circ = np.sqrt(mu / r_current) # For a prograde orbit, choose a tangential (perpendicular) direction: tangential_dir = np.array([-pos[1], pos[0]]) / r_current vel = v_circ * tangential_dir # instantaneous burn to circular orbit docking_event_time = t docking_event_coords = pos.copy() st.write(f"**Docking Event:** t = {t:.1f} s, Altitude = {(r_current - R_E)/1000:.1f} km, Circular Speed = {v_circ:.1f} m/s") # Record the current state: (time, x, y, phase) states.append((t, pos[0], pos[1], phase)) # --- Propagate the State (Euler Integration) --- a = acceleration(pos) pos = pos + vel * dt vel = vel + a * dt t += dt # Convert the recorded states to a NumPy array for easier slicing. states = np.array(states) # columns: time, x, y, phase # =============================== # Create the Animation Using Matplotlib # =============================== fig, ax = plt.subplots(figsize=(8, 8)) ax.set_aspect('equal') ax.set_xlabel("x (km)") ax.set_ylabel("y (km)") ax.set_title("Trajectory: Spin Launch & Orbital Attachment") # Draw Earth as a blue circle (Earth's radius in km) earth = plt.Circle((0, 0), R_E / 1000, color='blue', alpha=0.3, label="Earth") ax.add_artist(earth) # Set plot limits to show the trajectory (e.g., up to Earth's radius + 300 km) max_extent = (R_E + 300e3) / 1000 # in km ax.set_xlim(-max_extent, max_extent) ax.set_ylim(-max_extent, max_extent) # Initialize the trajectory line and payload marker for animation: trajectory_line, = ax.plot([], [], 'r-', lw=2, label="Trajectory") payload_marker, = ax.plot([], [], 'ko', markersize=5, label="Payload") time_text = ax.text(0.02, 0.95, '', transform=ax.transAxes, fontsize=10) def init(): trajectory_line.set_data([], []) payload_marker.set_data([], []) time_text.set_text('') return trajectory_line, payload_marker, time_text def update(frame): # Update the trajectory with all positions up to the current frame. t_val = states[frame, 0] x_vals = states[:frame+1, 1] / 1000 # convert m to km y_vals = states[:frame+1, 2] / 1000 # convert m to km trajectory_line.set_data(x_vals, y_vals) # Wrap coordinates in a list so that they are interpreted as sequences: payload_marker.set_data([states[frame, 1] / 1000], [states[frame, 2] / 1000]) time_text.set_text(f"Time: {t_val:.1f} s") return trajectory_line, payload_marker, time_text # Create the animation. Adjust the interval (ms) for playback speed. anim = FuncAnimation(fig, update, frames=len(states), init_func=init, interval=20, blit=True) # Convert the animation to an HTML5 video. video_html = anim.to_html5_video() if video_html: st.markdown("### Simulation Animation") # Option 1: Use streamlit components to embed the HTML video components.html(video_html, height=500) # Option 2: Alternatively, use st.markdown (uncomment the following line to try it) # st.markdown(video_html, unsafe_allow_html=True) else: st.error("No video generated. Please ensure that ffmpeg is installed and properly configured.") st.markdown( """ **Note:** This is a highly simplified simulation. In a real-world scenario, the spin‑launch, docking, and orbital insertion phases would involve much more complex physics including aerodynamics, non‑instantaneous burns, and detailed guidance and control. """ )