File size: 6,579 Bytes
5dce9ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import streamlit as st
import numpy as np
import matplotlib.pyplot as plt

# ===============================
# Streamlit Interface Setup
# ===============================
st.title("Spin Launch & Orbital Attachment Simulation")

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 chosen altitude, the payload “docks” with a human‑rated upper stage by
       instantly setting its velocity to that of a circular orbit.
       
    Adjust the parameters in the sidebar and click "Run Simulation" to see the result.
    """
)

# 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:
    # ===============================
    # 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
    # ===============================
    states = []  # Will store tuples of (time, x, y, phase)
    phase = 1    # Phase 1: spin-launch, Phase 2: orbital phase after docking
    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 is still moving outward (positive radial velocity), we trigger the docking.
        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 direction (perpendicular to the radial vector):
            tangential_dir = np.array([-pos[1], pos[0]]) / r_current
            vel = v_circ * tangential_dir  # Instantaneously change velocity for orbital insertion
            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 current state: (time, x, y, phase)
        states.append((t, pos[0], pos[1], phase))
        
        # --- Propagate the state using simple Euler integration ---
        a = acceleration(pos)
        pos = pos + vel * dt
        vel = vel + a * dt
        t += dt

    # Convert states to a NumPy array for easier slicing:
    states = np.array(states)  # columns: time, x, y, phase

    # ===============================
    # Create the Trajectory Plot with 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 and 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)

    # Define plot limits (extend to show orbit, e.g., Earth's radius plus 300 km):
    max_extent = (R_E + 300e3) / 1000  # in km
    ax.set_xlim(-max_extent, max_extent)
    ax.set_ylim(-max_extent, max_extent)

    # Separate the recorded states into spin-launch and orbital phases:
    spin_phase = states[states[:, 3] == 1]
    orbital_phase = states[states[:, 3] == 2]

    # Plot the spin-launch phase in red:
    if spin_phase.size > 0:
        ax.plot(spin_phase[:, 1] / 1000, spin_phase[:, 2] / 1000, 'r-', label="Spin‑Launch Phase")
    # Plot the orbital phase in green:
    if orbital_phase.size > 0:
        ax.plot(orbital_phase[:, 1] / 1000, orbital_phase[:, 2] / 1000, 'g-', label="Orbital Phase")

    # Mark the launch start:
    ax.plot(initial_position[0] / 1000, initial_position[1] / 1000, 'ko', label="Launch Start")
    # Mark the docking event, if it occurred:
    if docking_done and docking_event_coords is not None:
        ax.plot(docking_event_coords[0] / 1000, docking_event_coords[1] / 1000, 'bo', markersize=8, label="Docking Event")

    ax.legend()
    st.pyplot(fig)

    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).")