|
11 | 11 | "**Landed so far:**\n", |
12 | 12 | "1. `Stage` — wraps a single-stage `Rocket`\n", |
13 | 13 | "2. `Deployable` — a carried payload released mid-flight\n", |
14 | | - "3. `MultiStageRocket` — composes a stage + its deployables into one flight-ready `Rocket` (single active stage only, for now)\n" |
| 14 | + "3. `MultiStageRocket` — composes a stage + its deployables into one flight-ready `Rocket`\n", |
| 15 | + "4. `MultiStageRocket.flight_rocket` for more than one active stage (a booster with an inert sustainer riding on top)\n" |
15 | 16 | ] |
16 | 17 | }, |
17 | 18 | { |
18 | 19 | "cell_type": "code", |
19 | 20 | "execution_count": null, |
20 | 21 | "metadata": { |
21 | 22 | "execution": { |
22 | | - "iopub.execute_input": "2026-08-14T08:52:32.634504Z", |
23 | | - "iopub.status.busy": "2026-08-14T08:52:32.634292Z", |
24 | | - "iopub.status.idle": "2026-08-14T08:52:33.989471Z", |
25 | | - "shell.execute_reply": "2026-08-14T08:52:33.988977Z" |
| 23 | + "iopub.execute_input": "2026-08-14T09:07:27.168733Z", |
| 24 | + "iopub.status.busy": "2026-08-14T09:07:27.168494Z", |
| 25 | + "iopub.status.idle": "2026-08-14T09:07:28.612708Z", |
| 26 | + "shell.execute_reply": "2026-08-14T09:07:28.612298Z" |
26 | 27 | } |
27 | 28 | }, |
28 | 29 | "outputs": [], |
|
45 | 46 | "execution_count": null, |
46 | 47 | "metadata": { |
47 | 48 | "execution": { |
48 | | - "iopub.execute_input": "2026-08-14T08:52:33.991773Z", |
49 | | - "iopub.status.busy": "2026-08-14T08:52:33.991508Z", |
50 | | - "iopub.status.idle": "2026-08-14T08:52:34.015411Z", |
51 | | - "shell.execute_reply": "2026-08-14T08:52:34.015108Z" |
| 49 | + "iopub.execute_input": "2026-08-14T09:07:28.615144Z", |
| 50 | + "iopub.status.busy": "2026-08-14T09:07:28.614900Z", |
| 51 | + "iopub.status.idle": "2026-08-14T09:07:28.639194Z", |
| 52 | + "shell.execute_reply": "2026-08-14T09:07:28.638875Z" |
52 | 53 | } |
53 | 54 | }, |
54 | 55 | "outputs": [], |
|
101 | 102 | "execution_count": null, |
102 | 103 | "metadata": { |
103 | 104 | "execution": { |
104 | | - "iopub.execute_input": "2026-08-14T08:52:34.043634Z", |
105 | | - "iopub.status.busy": "2026-08-14T08:52:34.043428Z", |
106 | | - "iopub.status.idle": "2026-08-14T08:52:34.046144Z", |
107 | | - "shell.execute_reply": "2026-08-14T08:52:34.045821Z" |
| 105 | + "iopub.execute_input": "2026-08-14T09:07:28.666448Z", |
| 106 | + "iopub.status.busy": "2026-08-14T09:07:28.666250Z", |
| 107 | + "iopub.status.idle": "2026-08-14T09:07:28.668832Z", |
| 108 | + "shell.execute_reply": "2026-08-14T09:07:28.668508Z" |
108 | 109 | } |
109 | 110 | }, |
110 | 111 | "outputs": [], |
|
132 | 133 | "execution_count": null, |
133 | 134 | "metadata": { |
134 | 135 | "execution": { |
135 | | - "iopub.execute_input": "2026-08-14T08:52:34.047635Z", |
136 | | - "iopub.status.busy": "2026-08-14T08:52:34.047511Z", |
137 | | - "iopub.status.idle": "2026-08-14T08:52:34.049736Z", |
138 | | - "shell.execute_reply": "2026-08-14T08:52:34.049445Z" |
| 136 | + "iopub.execute_input": "2026-08-14T09:07:28.670308Z", |
| 137 | + "iopub.status.busy": "2026-08-14T09:07:28.670180Z", |
| 138 | + "iopub.status.idle": "2026-08-14T09:07:28.672458Z", |
| 139 | + "shell.execute_reply": "2026-08-14T09:07:28.672176Z" |
139 | 140 | } |
140 | 141 | }, |
141 | 142 | "outputs": [], |
|
158 | 159 | "execution_count": null, |
159 | 160 | "metadata": { |
160 | 161 | "execution": { |
161 | | - "iopub.execute_input": "2026-08-14T08:52:34.051054Z", |
162 | | - "iopub.status.busy": "2026-08-14T08:52:34.050931Z", |
163 | | - "iopub.status.idle": "2026-08-14T08:52:34.054057Z", |
164 | | - "shell.execute_reply": "2026-08-14T08:52:34.053793Z" |
| 162 | + "iopub.execute_input": "2026-08-14T09:07:28.673894Z", |
| 163 | + "iopub.status.busy": "2026-08-14T09:07:28.673778Z", |
| 164 | + "iopub.status.idle": "2026-08-14T09:07:28.676873Z", |
| 165 | + "shell.execute_reply": "2026-08-14T09:07:28.676536Z" |
165 | 166 | } |
166 | 167 | }, |
167 | 168 | "outputs": [], |
|
182 | 183 | "execution_count": null, |
183 | 184 | "metadata": { |
184 | 185 | "execution": { |
185 | | - "iopub.execute_input": "2026-08-14T08:52:34.055368Z", |
186 | | - "iopub.status.busy": "2026-08-14T08:52:34.055256Z", |
187 | | - "iopub.status.idle": "2026-08-14T08:52:34.057211Z", |
188 | | - "shell.execute_reply": "2026-08-14T08:52:34.056893Z" |
| 186 | + "iopub.execute_input": "2026-08-14T09:07:28.678288Z", |
| 187 | + "iopub.status.busy": "2026-08-14T09:07:28.678173Z", |
| 188 | + "iopub.status.idle": "2026-08-14T09:07:28.680214Z", |
| 189 | + "shell.execute_reply": "2026-08-14T09:07:28.679876Z" |
189 | 190 | } |
190 | 191 | }, |
191 | 192 | "outputs": [], |
|
209 | 210 | "execution_count": null, |
210 | 211 | "metadata": { |
211 | 212 | "execution": { |
212 | | - "iopub.execute_input": "2026-08-14T08:52:34.058606Z", |
213 | | - "iopub.status.busy": "2026-08-14T08:52:34.058510Z", |
214 | | - "iopub.status.idle": "2026-08-14T08:52:34.066016Z", |
215 | | - "shell.execute_reply": "2026-08-14T08:52:34.065749Z" |
| 213 | + "iopub.execute_input": "2026-08-14T09:07:28.681606Z", |
| 214 | + "iopub.status.busy": "2026-08-14T09:07:28.681507Z", |
| 215 | + "iopub.status.idle": "2026-08-14T09:07:28.689190Z", |
| 216 | + "shell.execute_reply": "2026-08-14T09:07:28.688895Z" |
216 | 217 | } |
217 | 218 | }, |
218 | 219 | "outputs": [], |
|
245 | 246 | ") < 1e-9" |
246 | 247 | ] |
247 | 248 | }, |
| 249 | + { |
| 250 | + "cell_type": "markdown", |
| 251 | + "metadata": {}, |
| 252 | + "source": [ |
| 253 | + "## Multiple active stages: a booster carrying an inert sustainer\n", |
| 254 | + "\n", |
| 255 | + "When more than one stage is still attached, `flight_rocket()` treats `active_stages[0]` as the currently firing stage (its motor becomes the composed `Rocket`'s own motor) and every other active stage as inert cargo riding along - contributing its *full* current mass (structure + motor + unburned propellant), since its own motor clock hasn't started yet.\n", |
| 256 | + "\n", |
| 257 | + "Each stage's own `Rocket` coordinate system is assumed to already be expressed in a shared stack frame - positions are used as-is. Deriving stack positions automatically from `interstage_lengths` and each stage's physical extent isn't implemented yet." |
| 258 | + ] |
| 259 | + }, |
| 260 | + { |
| 261 | + "cell_type": "code", |
| 262 | + "execution_count": null, |
| 263 | + "metadata": { |
| 264 | + "execution": { |
| 265 | + "iopub.execute_input": "2026-08-14T09:07:28.690554Z", |
| 266 | + "iopub.status.busy": "2026-08-14T09:07:28.690454Z", |
| 267 | + "iopub.status.idle": "2026-08-14T09:07:28.708806Z", |
| 268 | + "shell.execute_reply": "2026-08-14T09:07:28.708458Z" |
| 269 | + } |
| 270 | + }, |
| 271 | + "outputs": [], |
| 272 | + "source": [ |
| 273 | + "from rocketpy.motors.point_mass_motor import PointMassMotor\n", |
| 274 | + "\n", |
| 275 | + "booster_rocket = Rocket(\n", |
| 276 | + " radius=0.1,\n", |
| 277 | + " mass=10.0,\n", |
| 278 | + " inertia=(1.0, 1.0, 0.01),\n", |
| 279 | + " power_off_drag=0.5,\n", |
| 280 | + " power_on_drag=0.6,\n", |
| 281 | + " center_of_mass_without_motor=0.0,\n", |
| 282 | + ")\n", |
| 283 | + "booster_rocket.add_motor(\n", |
| 284 | + " PointMassMotor(\n", |
| 285 | + " thrust_source=100, dry_mass=1.0, propellant_initial_mass=2.0, burn_time=1.0\n", |
| 286 | + " ),\n", |
| 287 | + " position=0.0,\n", |
| 288 | + ")\n", |
| 289 | + "two_stage_booster = Stage(name=\"booster\", rocket=booster_rocket)\n", |
| 290 | + "\n", |
| 291 | + "sustainer_rocket = Rocket(\n", |
| 292 | + " radius=0.08,\n", |
| 293 | + " mass=5.0,\n", |
| 294 | + " inertia=(0.5, 0.5, 0.005),\n", |
| 295 | + " power_off_drag=0.3,\n", |
| 296 | + " power_on_drag=0.4,\n", |
| 297 | + " center_of_mass_without_motor=2.0,\n", |
| 298 | + ")\n", |
| 299 | + "sustainer_rocket.add_motor(\n", |
| 300 | + " PointMassMotor(\n", |
| 301 | + " thrust_source=50, dry_mass=0.5, propellant_initial_mass=1.0, burn_time=1.0\n", |
| 302 | + " ),\n", |
| 303 | + " position=2.0,\n", |
| 304 | + ")\n", |
| 305 | + "sustainer = Stage(name=\"sustainer\", rocket=sustainer_rocket)\n", |
| 306 | + "\n", |
| 307 | + "two_stage_vehicle = MultiStageRocket(stages=[two_stage_booster, sustainer])" |
| 308 | + ] |
| 309 | + }, |
| 310 | + { |
| 311 | + "cell_type": "code", |
| 312 | + "execution_count": null, |
| 313 | + "metadata": { |
| 314 | + "execution": { |
| 315 | + "iopub.execute_input": "2026-08-14T09:07:28.710231Z", |
| 316 | + "iopub.status.busy": "2026-08-14T09:07:28.710113Z", |
| 317 | + "iopub.status.idle": "2026-08-14T09:07:28.725482Z", |
| 318 | + "shell.execute_reply": "2026-08-14T09:07:28.725174Z" |
| 319 | + } |
| 320 | + }, |
| 321 | + "outputs": [], |
| 322 | + "source": [ |
| 323 | + "stacked = two_stage_vehicle.flight_rocket(\n", |
| 324 | + " active_stages=(two_stage_booster, sustainer)\n", |
| 325 | + ")\n", |
| 326 | + "print(f\"stacked.mass = {stacked.mass} kg (booster structure + full inert sustainer)\")\n", |
| 327 | + "print(f\"stacked.center_of_mass_without_motor = {stacked.center_of_mass_without_motor:.4f} m\")\n", |
| 328 | + "print(f\"stacked.motor is booster_rocket.motor -> {stacked.motor is booster_rocket.motor}\")\n", |
| 329 | + "\n", |
| 330 | + "after_separation = two_stage_vehicle.flight_rocket(active_stages=(sustainer,))\n", |
| 331 | + "print(f\"\\nafter_separation.mass = {after_separation.mass} kg (sustainer structure only)\")\n", |
| 332 | + "print(f\"after_separation.motor is sustainer_rocket.motor -> \"\n", |
| 333 | + " f\"{after_separation.motor is sustainer_rocket.motor}\")\n", |
| 334 | + "\n", |
| 335 | + "assert stacked.mass == 10.0 + (5.0 + 0.5 + 1.0)\n", |
| 336 | + "assert after_separation.mass == 5.0" |
| 337 | + ] |
| 338 | + }, |
248 | 339 | { |
249 | 340 | "cell_type": "markdown", |
250 | 341 | "metadata": {}, |
|
253 | 344 | "\n", |
254 | 345 | "Deliberately out of scope for the commits so far (see the roadmap in `mission_multistage_design.md`):\n", |
255 | 346 | "\n", |
256 | | - "- `flight_rocket()` for more than one active stage (booster + sustainer composed together, surfaces repositioned into stack coordinates)\n", |
| 347 | + "- Deriving stack positions from `interstage_lengths` and each stage's physical extent (stages are currently assumed to already share one coordinate frame)\n", |
257 | 348 | "- `Mission` — the orchestrator that runs one `Flight` per vehicle configuration with state handoff between them\n", |
258 | 349 | "- Deterministic time-based separation (motor burnout + delay) and apogee-triggered deployable ejection\n", |
259 | 350 | "- `StochasticMission`\n", |
|
0 commit comments