Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import numpy as np
|
3 |
+
import matplotlib.pyplot as plt
|
4 |
+
|
5 |
+
# ===============================
|
6 |
+
# Streamlit Interface Setup
|
7 |
+
# ===============================
|
8 |
+
st.title("Spin Launch & Orbital Attachment Simulation")
|
9 |
+
|
10 |
+
st.markdown(
|
11 |
+
"""
|
12 |
+
This simulation demonstrates a two‑phase launch:
|
13 |
+
|
14 |
+
1. **Spin‑Launch Phase:** A payload is accelerated from an elevated platform.
|
15 |
+
2. **Orbital Phase:** At a chosen altitude, the payload “docks” with a human‑rated upper stage by
|
16 |
+
instantly setting its velocity to that of a circular orbit.
|
17 |
+
|
18 |
+
Adjust the parameters in the sidebar and click "Run Simulation" to see the result.
|
19 |
+
"""
|
20 |
+
)
|
21 |
+
|
22 |
+
# Sidebar parameters for simulation
|
23 |
+
st.sidebar.header("Simulation Parameters")
|
24 |
+
|
25 |
+
# Time and integration parameters:
|
26 |
+
dt = st.sidebar.number_input("Time Step (s)", min_value=0.01, max_value=1.0, value=0.1, step=0.01)
|
27 |
+
t_max = st.sidebar.number_input("Total Simulation Time (s)", min_value=100, max_value=5000, value=1000, step=100)
|
28 |
+
|
29 |
+
# Launch conditions:
|
30 |
+
initial_altitude_km = st.sidebar.number_input("Initial Altitude (km)", min_value=1, max_value=100, value=50, step=1)
|
31 |
+
initial_velocity = st.sidebar.number_input("Initial Velocity (m/s)", min_value=100, max_value=5000, value=1200, step=100)
|
32 |
+
|
33 |
+
# Docking/Orbital attachment altitude:
|
34 |
+
docking_altitude_km = st.sidebar.number_input("Docking Altitude (km)", min_value=1, max_value=500, value=100, step=1)
|
35 |
+
|
36 |
+
# Button to run simulation
|
37 |
+
run_sim = st.sidebar.button("Run Simulation")
|
38 |
+
|
39 |
+
if run_sim:
|
40 |
+
# ===============================
|
41 |
+
# Physical Constants and Conversions
|
42 |
+
# ===============================
|
43 |
+
mu = 3.986e14 # Earth's gravitational parameter, m^3/s^2
|
44 |
+
R_E = 6.371e6 # Earth's radius, m
|
45 |
+
|
46 |
+
# Convert altitude values from km to m:
|
47 |
+
initial_altitude = initial_altitude_km * 1000.0 # initial altitude above Earth's surface (m)
|
48 |
+
docking_altitude = docking_altitude_km * 1000.0 # docking altitude above Earth's surface (m)
|
49 |
+
r_dock = R_E + docking_altitude # docking radius from Earth's center (m)
|
50 |
+
|
51 |
+
# ===============================
|
52 |
+
# Initial Conditions for the Simulation
|
53 |
+
# ===============================
|
54 |
+
# Starting at a point along the x-axis at a radial distance (Earth's radius + initial altitude)
|
55 |
+
initial_position = np.array([R_E + initial_altitude, 0.0])
|
56 |
+
# Initial velocity is chosen to be radial (pointing outward) at the chosen speed.
|
57 |
+
initial_velocity_vec = np.array([initial_velocity, 0.0])
|
58 |
+
|
59 |
+
# ===============================
|
60 |
+
# Define the Acceleration Function
|
61 |
+
# ===============================
|
62 |
+
def acceleration(pos):
|
63 |
+
"""
|
64 |
+
Compute acceleration due to Earth's gravity at position pos (in m).
|
65 |
+
"""
|
66 |
+
r = np.linalg.norm(pos)
|
67 |
+
return -mu * pos / r**3
|
68 |
+
|
69 |
+
# ===============================
|
70 |
+
# Run the Simulation Loop
|
71 |
+
# ===============================
|
72 |
+
states = [] # Will store tuples of (time, x, y, phase)
|
73 |
+
phase = 1 # Phase 1: spin-launch, Phase 2: orbital phase after docking
|
74 |
+
t = 0.0
|
75 |
+
pos = initial_position.copy()
|
76 |
+
vel = initial_velocity_vec.copy()
|
77 |
+
docking_done = False
|
78 |
+
docking_event_time = None
|
79 |
+
docking_event_coords = None
|
80 |
+
|
81 |
+
while t < t_max:
|
82 |
+
# --- Check for the Docking Event ---
|
83 |
+
# When the payload's distance from Earth's center exceeds the docking radius
|
84 |
+
# and it is still moving outward (positive radial velocity), we trigger the docking.
|
85 |
+
if phase == 1 and not docking_done and np.linalg.norm(pos) >= r_dock and vel.dot(pos) > 0:
|
86 |
+
docking_done = True
|
87 |
+
phase = 2 # Switch to orbital phase
|
88 |
+
r_current = np.linalg.norm(pos)
|
89 |
+
# Compute the circular orbital speed at the current radius:
|
90 |
+
v_circ = np.sqrt(mu / r_current)
|
91 |
+
# For a prograde orbit, choose a tangential direction (perpendicular to the radial vector):
|
92 |
+
tangential_dir = np.array([-pos[1], pos[0]]) / r_current
|
93 |
+
vel = v_circ * tangential_dir # Instantaneously change velocity for orbital insertion
|
94 |
+
docking_event_time = t
|
95 |
+
docking_event_coords = pos.copy()
|
96 |
+
st.write(f"**Docking Event:** t = {t:.1f} s, Altitude = {(r_current - R_E)/1000:.1f} km, Circular Speed = {v_circ:.1f} m/s")
|
97 |
+
|
98 |
+
# Record current state: (time, x, y, phase)
|
99 |
+
states.append((t, pos[0], pos[1], phase))
|
100 |
+
|
101 |
+
# --- Propagate the state using simple Euler integration ---
|
102 |
+
a = acceleration(pos)
|
103 |
+
pos = pos + vel * dt
|
104 |
+
vel = vel + a * dt
|
105 |
+
t += dt
|
106 |
+
|
107 |
+
# Convert states to a NumPy array for easier slicing:
|
108 |
+
states = np.array(states) # columns: time, x, y, phase
|
109 |
+
|
110 |
+
# ===============================
|
111 |
+
# Create the Trajectory Plot with Matplotlib
|
112 |
+
# ===============================
|
113 |
+
fig, ax = plt.subplots(figsize=(8, 8))
|
114 |
+
ax.set_aspect('equal')
|
115 |
+
ax.set_xlabel("x (km)")
|
116 |
+
ax.set_ylabel("y (km)")
|
117 |
+
ax.set_title("Trajectory: Spin Launch and Orbital Attachment")
|
118 |
+
|
119 |
+
# Draw Earth as a blue circle (Earth's radius in km)
|
120 |
+
earth = plt.Circle((0, 0), R_E / 1000, color='blue', alpha=0.3, label="Earth")
|
121 |
+
ax.add_artist(earth)
|
122 |
+
|
123 |
+
# Define plot limits (extend to show orbit, e.g., Earth's radius plus 300 km):
|
124 |
+
max_extent = (R_E + 300e3) / 1000 # in km
|
125 |
+
ax.set_xlim(-max_extent, max_extent)
|
126 |
+
ax.set_ylim(-max_extent, max_extent)
|
127 |
+
|
128 |
+
# Separate the recorded states into spin-launch and orbital phases:
|
129 |
+
spin_phase = states[states[:, 3] == 1]
|
130 |
+
orbital_phase = states[states[:, 3] == 2]
|
131 |
+
|
132 |
+
# Plot the spin-launch phase in red:
|
133 |
+
if spin_phase.size > 0:
|
134 |
+
ax.plot(spin_phase[:, 1] / 1000, spin_phase[:, 2] / 1000, 'r-', label="Spin‑Launch Phase")
|
135 |
+
# Plot the orbital phase in green:
|
136 |
+
if orbital_phase.size > 0:
|
137 |
+
ax.plot(orbital_phase[:, 1] / 1000, orbital_phase[:, 2] / 1000, 'g-', label="Orbital Phase")
|
138 |
+
|
139 |
+
# Mark the launch start:
|
140 |
+
ax.plot(initial_position[0] / 1000, initial_position[1] / 1000, 'ko', label="Launch Start")
|
141 |
+
# Mark the docking event, if it occurred:
|
142 |
+
if docking_done and docking_event_coords is not None:
|
143 |
+
ax.plot(docking_event_coords[0] / 1000, docking_event_coords[1] / 1000, 'bo', markersize=8, label="Docking Event")
|
144 |
+
|
145 |
+
ax.legend()
|
146 |
+
st.pyplot(fig)
|
147 |
+
|
148 |
+
st.markdown("**Note:** This is a simplified simulation. In a realistic system, the spin‑launch phase, docking maneuver, and orbital insertion would be modeled with far more complexity (including aerodynamic forces, gradual burns, and detailed guidance dynamics).")
|