|
6 | 6 | "source": [ |
7 | 7 | "# Multistage & Mission Architecture\n", |
8 | 8 | "\n", |
9 | | - "Demonstrates the multistage/mission architecture as it is built up commit by commit (see `mission_multistage_design.md`). Each section below corresponds to one landed commit and is added to as the next commit lands.\n", |
| 9 | + "Demonstrates the multistage/mission architecture as it is built up commit by commit (see `mission_multistage_design.md`). Each section below corresponds to one landed commit. This is the last item on the original roadmap - see \"Not built yet\" at the bottom for what's deliberately still out of scope.\n", |
10 | 10 | "\n", |
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 | 14 | "3. `MultiStageRocket` — composes a stage + its deployables into one flight-ready `Rocket`\n", |
15 | 15 | "4. `MultiStageRocket.flight_rocket` for more than one active stage (a booster with an inert sustainer riding on top)\n", |
16 | | - "5. `Mission` — orchestrates one `Flight` per vehicle configuration (single-stage degenerate case, and now a full two-stage mission: deterministic burnout+delay separation, state handoff, and sustainer ignition timing)\n" |
| 16 | + "5. `Mission` — orchestrates one `Flight` per vehicle configuration: the single-stage degenerate case, a full two-stage mission (deterministic burnout+delay separation, state handoff, ignition timing), and a deployable ejecting at apogee\n" |
17 | 17 | ] |
18 | 18 | }, |
19 | 19 | { |
20 | 20 | "cell_type": "code", |
21 | 21 | "execution_count": null, |
22 | 22 | "metadata": { |
23 | 23 | "execution": { |
24 | | - "iopub.execute_input": "2026-08-14T09:32:53.749600Z", |
25 | | - "iopub.status.busy": "2026-08-14T09:32:53.749365Z", |
26 | | - "iopub.status.idle": "2026-08-14T09:32:55.135838Z", |
27 | | - "shell.execute_reply": "2026-08-14T09:32:55.135430Z" |
| 24 | + "iopub.execute_input": "2026-08-14T09:43:41.487732Z", |
| 25 | + "iopub.status.busy": "2026-08-14T09:43:41.487551Z", |
| 26 | + "iopub.status.idle": "2026-08-14T09:43:42.862293Z", |
| 27 | + "shell.execute_reply": "2026-08-14T09:43:42.861881Z" |
28 | 28 | } |
29 | 29 | }, |
30 | 30 | "outputs": [], |
|
47 | 47 | "execution_count": null, |
48 | 48 | "metadata": { |
49 | 49 | "execution": { |
50 | | - "iopub.execute_input": "2026-08-14T09:32:55.138191Z", |
51 | | - "iopub.status.busy": "2026-08-14T09:32:55.137935Z", |
52 | | - "iopub.status.idle": "2026-08-14T09:32:55.162940Z", |
53 | | - "shell.execute_reply": "2026-08-14T09:32:55.162606Z" |
| 50 | + "iopub.execute_input": "2026-08-14T09:43:42.864593Z", |
| 51 | + "iopub.status.busy": "2026-08-14T09:43:42.864357Z", |
| 52 | + "iopub.status.idle": "2026-08-14T09:43:42.889043Z", |
| 53 | + "shell.execute_reply": "2026-08-14T09:43:42.888691Z" |
54 | 54 | } |
55 | 55 | }, |
56 | 56 | "outputs": [], |
|
103 | 103 | "execution_count": null, |
104 | 104 | "metadata": { |
105 | 105 | "execution": { |
106 | | - "iopub.execute_input": "2026-08-14T09:32:55.190343Z", |
107 | | - "iopub.status.busy": "2026-08-14T09:32:55.190147Z", |
108 | | - "iopub.status.idle": "2026-08-14T09:32:55.193022Z", |
109 | | - "shell.execute_reply": "2026-08-14T09:32:55.192688Z" |
| 106 | + "iopub.execute_input": "2026-08-14T09:43:42.917413Z", |
| 107 | + "iopub.status.busy": "2026-08-14T09:43:42.917223Z", |
| 108 | + "iopub.status.idle": "2026-08-14T09:43:42.919902Z", |
| 109 | + "shell.execute_reply": "2026-08-14T09:43:42.919550Z" |
110 | 110 | } |
111 | 111 | }, |
112 | 112 | "outputs": [], |
|
134 | 134 | "execution_count": null, |
135 | 135 | "metadata": { |
136 | 136 | "execution": { |
137 | | - "iopub.execute_input": "2026-08-14T09:32:55.194536Z", |
138 | | - "iopub.status.busy": "2026-08-14T09:32:55.194412Z", |
139 | | - "iopub.status.idle": "2026-08-14T09:32:55.196728Z", |
140 | | - "shell.execute_reply": "2026-08-14T09:32:55.196474Z" |
| 137 | + "iopub.execute_input": "2026-08-14T09:43:42.921466Z", |
| 138 | + "iopub.status.busy": "2026-08-14T09:43:42.921344Z", |
| 139 | + "iopub.status.idle": "2026-08-14T09:43:42.923802Z", |
| 140 | + "shell.execute_reply": "2026-08-14T09:43:42.923374Z" |
141 | 141 | } |
142 | 142 | }, |
143 | 143 | "outputs": [], |
|
160 | 160 | "execution_count": null, |
161 | 161 | "metadata": { |
162 | 162 | "execution": { |
163 | | - "iopub.execute_input": "2026-08-14T09:32:55.198195Z", |
164 | | - "iopub.status.busy": "2026-08-14T09:32:55.198072Z", |
165 | | - "iopub.status.idle": "2026-08-14T09:32:55.201107Z", |
166 | | - "shell.execute_reply": "2026-08-14T09:32:55.200801Z" |
| 163 | + "iopub.execute_input": "2026-08-14T09:43:42.925422Z", |
| 164 | + "iopub.status.busy": "2026-08-14T09:43:42.925275Z", |
| 165 | + "iopub.status.idle": "2026-08-14T09:43:42.928373Z", |
| 166 | + "shell.execute_reply": "2026-08-14T09:43:42.928094Z" |
167 | 167 | } |
168 | 168 | }, |
169 | 169 | "outputs": [], |
|
184 | 184 | "execution_count": null, |
185 | 185 | "metadata": { |
186 | 186 | "execution": { |
187 | | - "iopub.execute_input": "2026-08-14T09:32:55.202470Z", |
188 | | - "iopub.status.busy": "2026-08-14T09:32:55.202372Z", |
189 | | - "iopub.status.idle": "2026-08-14T09:32:55.204229Z", |
190 | | - "shell.execute_reply": "2026-08-14T09:32:55.203950Z" |
| 187 | + "iopub.execute_input": "2026-08-14T09:43:42.929756Z", |
| 188 | + "iopub.status.busy": "2026-08-14T09:43:42.929637Z", |
| 189 | + "iopub.status.idle": "2026-08-14T09:43:42.931975Z", |
| 190 | + "shell.execute_reply": "2026-08-14T09:43:42.931472Z" |
191 | 191 | } |
192 | 192 | }, |
193 | 193 | "outputs": [], |
|
211 | 211 | "execution_count": null, |
212 | 212 | "metadata": { |
213 | 213 | "execution": { |
214 | | - "iopub.execute_input": "2026-08-14T09:32:55.205694Z", |
215 | | - "iopub.status.busy": "2026-08-14T09:32:55.205590Z", |
216 | | - "iopub.status.idle": "2026-08-14T09:32:55.214233Z", |
217 | | - "shell.execute_reply": "2026-08-14T09:32:55.213919Z" |
| 214 | + "iopub.execute_input": "2026-08-14T09:43:42.933547Z", |
| 215 | + "iopub.status.busy": "2026-08-14T09:43:42.933420Z", |
| 216 | + "iopub.status.idle": "2026-08-14T09:43:42.941719Z", |
| 217 | + "shell.execute_reply": "2026-08-14T09:43:42.941413Z" |
218 | 218 | } |
219 | 219 | }, |
220 | 220 | "outputs": [], |
|
263 | 263 | "execution_count": null, |
264 | 264 | "metadata": { |
265 | 265 | "execution": { |
266 | | - "iopub.execute_input": "2026-08-14T09:32:55.215684Z", |
267 | | - "iopub.status.busy": "2026-08-14T09:32:55.215576Z", |
268 | | - "iopub.status.idle": "2026-08-14T09:32:55.233550Z", |
269 | | - "shell.execute_reply": "2026-08-14T09:32:55.233244Z" |
| 266 | + "iopub.execute_input": "2026-08-14T09:43:42.943171Z", |
| 267 | + "iopub.status.busy": "2026-08-14T09:43:42.943050Z", |
| 268 | + "iopub.status.idle": "2026-08-14T09:43:42.961567Z", |
| 269 | + "shell.execute_reply": "2026-08-14T09:43:42.961261Z" |
270 | 270 | } |
271 | 271 | }, |
272 | 272 | "outputs": [], |
|
313 | 313 | "execution_count": null, |
314 | 314 | "metadata": { |
315 | 315 | "execution": { |
316 | | - "iopub.execute_input": "2026-08-14T09:32:55.234958Z", |
317 | | - "iopub.status.busy": "2026-08-14T09:32:55.234836Z", |
318 | | - "iopub.status.idle": "2026-08-14T09:32:55.250443Z", |
319 | | - "shell.execute_reply": "2026-08-14T09:32:55.250148Z" |
| 316 | + "iopub.execute_input": "2026-08-14T09:43:42.963017Z", |
| 317 | + "iopub.status.busy": "2026-08-14T09:43:42.962909Z", |
| 318 | + "iopub.status.idle": "2026-08-14T09:43:42.978386Z", |
| 319 | + "shell.execute_reply": "2026-08-14T09:43:42.978094Z" |
320 | 320 | } |
321 | 321 | }, |
322 | 322 | "outputs": [], |
|
353 | 353 | "execution_count": null, |
354 | 354 | "metadata": { |
355 | 355 | "execution": { |
356 | | - "iopub.execute_input": "2026-08-14T09:32:55.251978Z", |
357 | | - "iopub.status.busy": "2026-08-14T09:32:55.251876Z", |
358 | | - "iopub.status.idle": "2026-08-14T09:32:55.314671Z", |
359 | | - "shell.execute_reply": "2026-08-14T09:32:55.314335Z" |
| 356 | + "iopub.execute_input": "2026-08-14T09:43:42.979794Z", |
| 357 | + "iopub.status.busy": "2026-08-14T09:43:42.979692Z", |
| 358 | + "iopub.status.idle": "2026-08-14T09:43:43.042549Z", |
| 359 | + "shell.execute_reply": "2026-08-14T09:43:43.042227Z" |
360 | 360 | } |
361 | 361 | }, |
362 | 362 | "outputs": [], |
|
395 | 395 | "execution_count": null, |
396 | 396 | "metadata": { |
397 | 397 | "execution": { |
398 | | - "iopub.execute_input": "2026-08-14T09:32:55.316243Z", |
399 | | - "iopub.status.busy": "2026-08-14T09:32:55.316120Z", |
400 | | - "iopub.status.idle": "2026-08-14T09:32:55.334402Z", |
401 | | - "shell.execute_reply": "2026-08-14T09:32:55.334090Z" |
| 398 | + "iopub.execute_input": "2026-08-14T09:43:43.044565Z", |
| 399 | + "iopub.status.busy": "2026-08-14T09:43:43.044439Z", |
| 400 | + "iopub.status.idle": "2026-08-14T09:43:43.062916Z", |
| 401 | + "shell.execute_reply": "2026-08-14T09:43:43.062594Z" |
402 | 402 | } |
403 | 403 | }, |
404 | 404 | "outputs": [], |
|
447 | 447 | "execution_count": null, |
448 | 448 | "metadata": { |
449 | 449 | "execution": { |
450 | | - "iopub.execute_input": "2026-08-14T09:32:55.335889Z", |
451 | | - "iopub.status.busy": "2026-08-14T09:32:55.335782Z", |
452 | | - "iopub.status.idle": "2026-08-14T09:32:55.377981Z", |
453 | | - "shell.execute_reply": "2026-08-14T09:32:55.377662Z" |
| 450 | + "iopub.execute_input": "2026-08-14T09:43:43.064399Z", |
| 451 | + "iopub.status.busy": "2026-08-14T09:43:43.064295Z", |
| 452 | + "iopub.status.idle": "2026-08-14T09:43:43.106120Z", |
| 453 | + "shell.execute_reply": "2026-08-14T09:43:43.105806Z" |
454 | 454 | } |
455 | 455 | }, |
456 | 456 | "outputs": [], |
|
478 | 478 | " print(f\" t={time:8.3f}s {event}\")" |
479 | 479 | ] |
480 | 480 | }, |
| 481 | + { |
| 482 | + "cell_type": "markdown", |
| 483 | + "metadata": {}, |
| 484 | + "source": [ |
| 485 | + "## `Mission`: a deployable ejecting at apogee\n", |
| 486 | + "\n", |
| 487 | + "A single-stage vehicle with exactly one deployable whose `ejection` is the string `\"apogee\"` runs a different three-`Flight` sequence: the carrier stage flying with the deployable aboard (using the existing `terminate_on_apogee` flag on `Flight` - no new event machinery needed), then two children from the apogee state onward - the carrier stage continuing without the deployable's mass, and the deployable flying its own `free_rocket`.\n", |
| 488 | + "\n", |
| 489 | + "Same `_handoff_state` transform as the two-stage case, and the same momentum-conserving delta_v split, just generalized: any two children of a split share `separation_delta_v` in inverse proportion to their post-split masses.\n", |
| 490 | + "\n", |
| 491 | + "`ejection` other than `\"apogee\"`, and deployables defined via `add_surface()` instead of `free_rocket`, aren't supported yet." |
| 492 | + ] |
| 493 | + }, |
| 494 | + { |
| 495 | + "cell_type": "code", |
| 496 | + "execution_count": null, |
| 497 | + "metadata": { |
| 498 | + "execution": { |
| 499 | + "iopub.execute_input": "2026-08-14T09:43:43.107691Z", |
| 500 | + "iopub.status.busy": "2026-08-14T09:43:43.107585Z", |
| 501 | + "iopub.status.idle": "2026-08-14T09:43:43.123541Z", |
| 502 | + "shell.execute_reply": "2026-08-14T09:43:43.123192Z" |
| 503 | + } |
| 504 | + }, |
| 505 | + "outputs": [], |
| 506 | + "source": [ |
| 507 | + "carrier_rocket = Rocket(\n", |
| 508 | + " radius=0.1,\n", |
| 509 | + " mass=10.0,\n", |
| 510 | + " inertia=(1.0, 1.0, 0.01),\n", |
| 511 | + " power_off_drag=0.5,\n", |
| 512 | + " power_on_drag=0.6,\n", |
| 513 | + " center_of_mass_without_motor=0.0,\n", |
| 514 | + ")\n", |
| 515 | + "carrier_rocket.add_motor(\n", |
| 516 | + " PointMassMotor(\n", |
| 517 | + " thrust_source=400, dry_mass=1.0, propellant_initial_mass=2.0, burn_time=1.0\n", |
| 518 | + " ),\n", |
| 519 | + " position=0.0,\n", |
| 520 | + ")\n", |
| 521 | + "carrier = Stage(name=\"carrier\", rocket=carrier_rocket)\n", |
| 522 | + "\n", |
| 523 | + "payload_rocket = Rocket(\n", |
| 524 | + " radius=0.02,\n", |
| 525 | + " mass=1.0,\n", |
| 526 | + " inertia=(0.001, 0.001, 0.0001),\n", |
| 527 | + " power_off_drag=0.5,\n", |
| 528 | + " power_on_drag=0.5,\n", |
| 529 | + " center_of_mass_without_motor=0.0,\n", |
| 530 | + ")\n", |
| 531 | + "\n", |
| 532 | + "deployable_vehicle = MultiStageRocket(stages=[carrier])\n", |
| 533 | + "deployable_vehicle.add_deployable(\n", |
| 534 | + " name=\"payload\",\n", |
| 535 | + " mass=1.0,\n", |
| 536 | + " inertia=(0.001, 0.001, 0.0001),\n", |
| 537 | + " position=1.0,\n", |
| 538 | + " free_rocket=payload_rocket,\n", |
| 539 | + " ejection=\"apogee\",\n", |
| 540 | + ")" |
| 541 | + ] |
| 542 | + }, |
| 543 | + { |
| 544 | + "cell_type": "code", |
| 545 | + "execution_count": null, |
| 546 | + "metadata": { |
| 547 | + "execution": { |
| 548 | + "iopub.execute_input": "2026-08-14T09:43:43.124953Z", |
| 549 | + "iopub.status.busy": "2026-08-14T09:43:43.124823Z", |
| 550 | + "iopub.status.idle": "2026-08-14T09:43:43.148440Z", |
| 551 | + "shell.execute_reply": "2026-08-14T09:43:43.148160Z" |
| 552 | + } |
| 553 | + }, |
| 554 | + "outputs": [], |
| 555 | + "source": [ |
| 556 | + "deployable_mission = Mission(\n", |
| 557 | + " vehicle=deployable_vehicle,\n", |
| 558 | + " environment=Environment(),\n", |
| 559 | + " rail_length=1.0,\n", |
| 560 | + " inclination=90,\n", |
| 561 | + " heading=0,\n", |
| 562 | + " max_time=20,\n", |
| 563 | + ")\n", |
| 564 | + "\n", |
| 565 | + "print(f\"bodies flown: {list(deployable_mission.flights.keys())}\")\n", |
| 566 | + "print(\n", |
| 567 | + " \"'carrier' flights (with payload, then alone): \"\n", |
| 568 | + " f\"{len(deployable_mission.flights['carrier'])}\"\n", |
| 569 | + ")\n", |
| 570 | + "print(\n", |
| 571 | + " \"'payload' flights (with carrier, then alone): \"\n", |
| 572 | + " f\"{len(deployable_mission.flights['payload'])}\"\n", |
| 573 | + ")\n", |
| 574 | + "print(\"\\ntimeline:\")\n", |
| 575 | + "for time, event in deployable_mission.timeline:\n", |
| 576 | + " print(f\" t={time:8.3f}s {event}\")" |
| 577 | + ] |
| 578 | + }, |
481 | 579 | { |
482 | 580 | "cell_type": "markdown", |
483 | 581 | "metadata": {}, |
484 | 582 | "source": [ |
485 | 583 | "## Not built yet\n", |
486 | 584 | "\n", |
487 | | - "Deliberately out of scope for the commits so far (see the roadmap in `mission_multistage_design.md`):\n", |
| 585 | + "Deliberately out of scope, not part of the original roadmap's 8 commits (see `mission_multistage_design.md`):\n", |
488 | 586 | "\n", |
| 587 | + "- More than two stages, or a deployable combined with a multi-stage vehicle\n", |
489 | 588 | "- Deriving stack positions from `interstage_lengths` and each stage's physical extent (stages are currently assumed to already share one coordinate frame)\n", |
490 | | - "- `Mission` for more than two stages\n", |
491 | | - "- `Mission` deployable ejection (apogee-triggered, via `terminate_on_apogee`)\n", |
492 | | - "- Deterministic time-based separation (motor burnout + delay) and apogee-triggered deployable ejection\n", |
493 | | - "- `StochasticMission`\n", |
494 | | - "\n", |
495 | | - "Each lands as its own commit with its own tests; this notebook grows alongside them." |
| 589 | + "- Event-triggered separation/ignition/ejection beyond deterministic delays and `\"apogee\"` (no generic mid-flight trigger/event solver exists in Flight today)\n", |
| 590 | + "- Deployables built from `add_surface()` instead of `free_rocket`\n", |
| 591 | + "- `StochasticMission`, `to_dict`/`from_dict`, `draw()`/`info()`\n" |
496 | 592 | ] |
497 | 593 | } |
498 | 594 | ], |
|
0 commit comments