Week 10 — 推進系統
week10_propulsion/基礎知識導論
Week 10 基礎知識導論 — 推進、Delta-V 與 Astrogator MCS
軌道機動的貨幣是 delta-V。任務規劃的一半工作,
就是把有限的 delta-V 預算分配給抬升、圓化、維持與去軌。
1. 這一週在解什麼問題
FIREYE 要從入軌高度移到任務軌道、在兩年任務期間對抗大氣阻力、
並在任務結束後把自己降下來。三件事各要多少 delta-V?推進劑要帶多少?
本週用 STK Astrogator 的任務控制序列 (MCS) 與差分修正器求解,
再用 Python 的解析式(Hohmann + 火箭方程)做量級交叉驗證。
2. 核心觀念
2.1 火箭方程
Δv = Isp * g0 * ln( m0 / mf ) g0 = 9.80665 m/s²
反過來求推進劑質量:
m_prop = m0 * ( 1 - exp( -Δv / (Isp * g0) ) )
指數關係是關鍵:delta-V 需求線性增加,推進劑質量指數增加。
所以任務設計的第一原則永遠是「先想辦法不要用 delta-V」
(選對入軌軌道、用氣阻自然去軌、用被動姿態)。
2.2 CubeSat 推進系統的選擇
| 類型 | Isp (s) | 推力 | CubeSat 適用性 |
|---|---|---|---|
| 冷氣體 (cold gas) | 40–70 | mN | 簡單安全,但 Isp 太低 |
| 綠色單推進劑 | 200–250 | mN–N | 需要加熱與觸媒床 |
| 肼類單推進劑 | 200–230 | N | 毒性,發射審查困難 |
| 電熱 (resistojet) | 50–150 | mN | 功率需求中等 |
| 電噴 (electrospray) | 800–2000 | µN | 推力極小,機動要數天 |
| 離子 / 霍爾 | 1000–3000 | mN | 功率需求高(數十至數百 W) |
CubeSat 的推進是被功率與安全審查綁死的,不是被物理綁死的。
6U 常見功率只有 10–30 W,離子推進的功率需求往往直接出局。
2.3 Hohmann 轉移:兩次點火的最省路徑
從半徑 r1 圓軌道轉到 r2 圓軌道:
Δv1 = sqrt(μ/r1) * ( sqrt( 2*r2 / (r1+r2) ) - 1 ) 抬升(在 r1 點火)
Δv2 = sqrt(μ/r2) * ( 1 - sqrt( 2*r1 / (r1+r2) ) ) 圓化(在 r2 點火)
Δv_total = Δv1 + Δv2
轉移時間是轉移橢圓週期的一半。
Hohmann 在共面圓軌道之間是最省的兩脈衝解(在 r2/r1 < 11.94 時)。
2.4 脈衝 vs 有限推力
解析式假設瞬時脈衝(速度瞬間改變,位置不變)。
真實推進器要燒幾分鐘甚至幾小時,期間衛星一直在移動:
- 產生 gravity loss:部分推力用在對抗重力方向的位置變化
- 低推力(電推)的損失可能達 5–15%,冷氣體與化學推進通常 < 2%
Astrogator 的 Finite Maneuver 段可以模擬這件事,
而 Python 的解析式不行。這是本週兩者差異的來源之一。
2.5 攝動:為什麼 STK 的答案跟手算不同
本教材的實測對照:
| 機動 | Python 解析(兩體) | STK Astrogator(HPOP) | 差異 |
|---|---|---|---|
| 抬遠地點 | 41.8 m/s | 38.97 m/s | −6.8% |
| 圓化 | 41.6 m/s | 38.99 m/s | −6.3% |
| 去軌 | 143.3 m/s | 140.56 m/s | −1.9% |
| 總計 | 233.0 m/s | 218.5 m/s | −6.2% |
差異來自兩體 vs 含攝動的力學模型。
解析式的用途是量級驗證(如果差 10 倍就是有人算錯),
不是取代高精度傳播。
另外,軌道維持的 6.2 m/s 是年度預算,由大氣阻力決定,
它是一連串小修正的總和,沒有對應的單一 MCS 段。
2.6 Astrogator 的 MCS 結構
MCS (Mission Control Sequence) 是一串段 (segment):
| 段型 | 作用 |
|---|---|
| Initial State | 起始軌道狀態 |
| Propagate | 傳播到某個停止條件 |
| Maneuver | 施加 delta-V(脈衝或有限推力) |
| Target Sequence | 包住一組段,用差分修正器求解未知數 |
| Return / Stop | 結束 |
停止條件 (Stopping Condition) 是 Astrogator 的靈魂:
不是「傳播 90 分鐘」,而是「傳播到 Apoapsis」、「傳播到 Altitude = 550 km」。
這讓 MCS 具有物理意義,也讓它在參數改變後仍然正確。
2.7 差分修正器 (Differential Corrector)
要解的是一個打靶問題:
控制參數 (Control) → [傳播] → 結果 (Constraint)
例:Maneuver 的 ΔV 大小 例:遠地點高度 = 550 km
差分修正器用數值微分建立 Jacobian,牛頓法迭代:
Δcontrol = J⁻¹ * ( target - current )
成功的判準是 Status == "Converged",不是「跑完了」。
沒收斂的 MCS 一樣會產生結果,只是那個結果不滿足約束 —— 又一個靜默失敗。
本教材踩過的兩個坑:
- ΔV 讀成 0 — 收斂後的值在差分修正器控制項的
FinalValue上,
不在 Maneuver 段的名目欄位上。 - DV2 收斂到錯的分支(1946 m/s) — 在遠地點用
"Radius Of Periapsis" 當約束是退化的(該點對近地點半徑的靈敏度接近零),
改用 Eccentricity = 0 才收斂到正確的 39 m/s。
第 2 點是打靶問題的通則:約束的選擇要讓 Jacobian 良態。
2.8 去軌:不是選項,是義務
國際準則(IADC)長期要求 LEO 任務在任務結束後 25 年內再入;
美國 FCC 已對其管轄的部分任務收緊到 5 年。
CubeSat 常見的三種作法:
- 自然衰減 — 選夠低的軌道讓氣阻處理(約 500 km 以下數年)
- 主動去軌 — 用推進把近地點降到大氣層(本週的第三次點火)
- 增阻裝置 — 展開帆或氣球,增加面積質量比
本週的 MCS 第三段就是把近地點降到 RE + 50 km,需要約 140 m/s。
這筆 delta-V 必須在任務一開始就列進預算,不能事後才想。
3. 名詞與符號
| 符號 | 意義 | 單位 | 典型值 |
|---|---|---|---|
Δv |
速度增量 | m/s | 本任務總計約 220–233 |
Isp |
比衝 | s | 見 2.2 |
g0 |
標準重力 | m/s² | 9.80665 |
μ |
地球重力參數 | km³/s² | 398600.4418 |
| MCS | 任務控制序列 | — | Astrogator 的主結構 |
| DC | 差分修正器 | — | 判準是 Converged |
| Stopping Condition | 停止條件 | — | Apoapsis / Altitude / Duration |
m0 / mf |
初 / 末質量 | kg | — |
4. 觀念 → 本週實驗對照
| 觀念 | 出現在本週的哪裡 |
|---|---|
| MCS 三段機動 | week10_propulsion_astrogator.txt(75 條指令,含去軌) |
| 差分修正器 | 同上,三個 Target Sequence |
| 解析交叉驗證 | week10_propulsion_deltav.py,Hohmann + 火箭方程 |
| 圖表 | week10_deltav.png |
| 段序 / 停止條件 / 收斂 / ΔV 回讀 | week10_verify.py,42 項(36 PASS / 6 SKIP) |
腳本層面的兩個實作陷阱(教材已修正):
SetProp會自動建立預設 MCS 段,必須先DeleteSegment清掉;
插入點寫成裸的-會漂進 Target Sequence 裡,要寫成
InsertSegment MainSequence.SegmentList.- X指定完整路徑。
5. 常見誤解
- 「delta-V 是燃料。」 — delta-V 是速度增量,燃料是它經過火箭方程(指數)的結果。
同樣的 delta-V,Isp 差 3 倍,燃料差很多。 - 「MCS 跑完就是解出來了。」 — 要檢查
Status == "Converged"。沒收斂也會有結果。 - 「解析式跟 STK 差 6%,一定是 STK 錯了。」 — 反過來想:解析式忽略了攝動與有限推力。
6% 是合理的模型差,不是 bug。差 600% 才是 bug。 - 「去軌之後再想就好。」 — 去軌 delta-V 是本任務最大的一筆(140 m/s,佔 64%)。
事後才加會直接壓垮質量預算。 - 「約束隨便選一個就好。」 — 見 2.7,退化的約束會讓修正器收斂到荒謬的分支,
而且它會告訴你「收斂了」。
6. 延伸閱讀
軌道機動
- Wikipedia: Hohmann transfer orbit
— 公式與推導,以及r2/r1 > 11.94時雙橢圓轉移更省的有趣結果。 - Wikipedia: Tsiolkovsky rocket equation
- Braeunig, Rocket & Space Technology
— 免費、完整的軌道機動與推進計算。 - 教科書:Curtis, Orbital Mechanics for Engineering Students 第 6 章;
Vallado, Fundamentals of Astrodynamics and Applications。
推進系統
- NASA State of the Art of Small Spacecraft Technology
— Propulsion 一章,市售 CubeSat 推進模組的 Isp、推力、功率、體積實際數字。 - NASA Technical Reports Server (NTRS)
— 搜尋 "CubeSat propulsion survey"。
去軌與太空碎片
- IADC(機構間太空碎片協調委員會) — 去軌準則的來源。
- NASA 軌道碎片計畫辦公室 — 季刊與碎片環境模型。
- ESA Space Debris — 歐洲視角與年度環境報告。
工具
- Ansys STK 線上說明 — Astrogator 完整說明。
本機:C:\Program Files\AGI\STK 12\Help\Content\astrogator.htm,
以及Help\LinkedDocuments\STK_Astrogator_OM_Tutorial.pdf(物件模型教學)。 - NASA GMAT
— 免費的任務分析軟體,MCS 概念與 Astrogator 幾乎一對一,可用來對答案。
7. 課前自我檢核
- 用火箭方程算:6 kg 衛星、Isp = 220 s、需要 220 m/s,推進劑要幾克?
- 為什麼 Hohmann 是「最省」?它在什麼條件下不是最省?
- 差分修正器回報收斂,但 delta-V 是 1946 m/s。你會怎麼判斷這是不是對的?
- 去軌 delta-V 佔本任務總預算的比例是多少?如果改用增阻裝置,這筆會變成什麼成本?
- 解析式與 STK 差 6.2%。列出三個可能的來源,並說明怎麼逐一排除。
Week10_15_Propulsion_Launch_Handouts.md
實驗單:Week 10 推進系統 / Week 15 發射與部署
兩週皆採雙軌(同 Week 5/8 規格):Python 解析保證可跑並出圖,STK 付費模組
(Astrogator / Aviator)為正牌高精度路徑。程式內附 STK Connect / ansys.stk.core 範本。
Week 10 — 推進系統(delta-V 預算與機動)
檔案:week10_propulsion_deltav.py → week10_deltav.png
參考:官方 Astrogator MCS snippet;程式尾 ASTROGATOR_MCS_CONNECT。
目標:為 CubeSat 任務建 delta-V 預算,並用火箭方程反推推進劑質量。
Python 那軌算什麼:
- Hohmann 轉移(部署軌道 → 任務軌道)兩次 delta-V 與轉移時間。
- 軌道維持(低軌阻力補償)年 delta-V。
- Deorbit(降近地點至再入)delta-V。
- 火箭方程:總 delta-V + 乾質量 + Isp → 推進劑質量。
基準結果(400→550 km,1 年任務,乾重 4 kg,Isp 220 s):總 ~233 m/s,推進劑 ~456 g。
操作:
1. 跑基準,判讀三張圖(delta-V 堆疊 / 推進劑 vs Isp / Hohmann 幾何)。
2. Part B:改推進技術 Isp(冷氣 70 / 單推 220 / 雙推 300 / 電推 1200 s),看推進劑質量差異——體會為何深空/長壽命任務用電推。
3. Part C(需 Astrogator):執行 week10_propulsion_astrogator.txt,用 MCS + 差分修正器求
三次 burn(抬遠地點 / 圓化 / deorbit)的 delta-V,對比 Python 解析值。
交付:delta-V 預算表 + 推進劑質量 + 推進技術選型 + (選作)Astrogator 對比。
評分:delta-V 計算 30% / 火箭方程 25% / 技術選型 25% / Astrogator 對比 20%。
Python 解析 vs STK/Astrogator 實測對照(本機 STK 12.10.0 跑出的數值):
| 項目 | Python 解析(兩體) | STK Astrogator(HPOP + 差分修正) | 差異 |
|---|---|---|---|
| burn 1 抬遠地點 | 41.8 m/s | 38.97 m/s | −6.8 % |
| burn 2 圓化 | 41.6 m/s | 38.99 m/s | −6.3 % |
| deorbit 降近地點 | 143.3 m/s | −140.56 m/s(逆向) | −1.9 % |
| 軌道維持(1 年阻力) | 6.2 m/s | 無對應段(年度預算,非單次機動) | — |
| 合計 | 233.0 m/s | 218.5 m/s | −6.2 % |
差異來源:Python 用理想兩體 Hohmann,STK 用 Earth HPOP Default v10 含 J2 等攝動,
且轉移段起點是實際傳播到的近地點而非理想圓軌道。討論題:哪一個該拿去做推進劑採購決策?
末軌驗證:Deorbit.FinalState.Keplerian.PeriapsisRadSize = 6428.19 km,
命中目標 6428.137 km(= RE + 50 km),容差 1 km。
已知限制:FinalState.FuelMass 仍是初始的 0.5 kg —— Isp 屬於 EngineModel 元件
(Constant Thrust and Isp),Connect 無法直接寫入,要在元件瀏覽器改或用物件模型。
所以推進劑消耗目前只能由 Python 的火箭方程給(456 g)。
Week 15 — 發射與部署
檔案:week15_launch_deploy.py → week15_launch.png
參考:LaunchVehicleTradeStudiesAviator.py(Aviator 三重迴圈 trade study);
TASA...STKEngine示範.ipynb(ansys.stk.core 彈道發射)。程式尾附兩者骨架。
目標:用兩節火箭的火箭方程做 trade study,看能送多少酬載到目標 LEO;
理解發射後部署與首次通聯幾何。
Python 那軌算什麼:
- 目標軌道所需 delta-V(軌道速度 + 隨高度增加的損失)。
- 兩節火箭酬載能力(逐節火箭方程,二分搜尋酬載)。
- Trade 1:第二節推進劑質量(staging)vs 酬載。
- Trade 2:目標高度 vs 酬載(高度越高酬載越少)。
- 部署後圓軌道週期與地平可見幾何。
基準結果(目標 550 km):所需 ~9.7 km/s,基準兩節火箭送 ~3 t;高度越高酬載越少。
操作:
1. 跑基準,判讀三張圖(staging trade / altitude trade / 部署軌道)。
2. Part B:改各節 Isp/結構比,看對酬載的敏感度;找最佳 staging。
3. Part C(需 Aviator + Astrogator):照 LaunchVehicleTradeStudiesAviator.py 的三重迴圈
(燃料 × 發射方位 × pitch FPA rate),用 DataProviders 讀入軌 apogee/perigee/inclination,
以 perigee > 100 km 為有效入軌判據;或照 TASA 範本建彈道發射軌跡並匯出星曆。
4. 部署後:用 Week 14 的 pass scheduler 算 CubeSat 入軌後對地面站的首次通聯。
交付:酬載 trade study 表/圖 + 最佳 staging + (選作)STK 發射軌跡 + 首次通聯時刻。
評分:所需 delta-V 30% / 酬載 trade 30% / STK 發射軌跡 25% / 首次通聯串接 15%。
兩週的授權提醒
- Week 10 Part C 需 STK/Astrogator;Week 15 Part C 需 STK/Aviator(+Astrogator)。
兩者皆付費模組——若學校無授權,A/B 兩軌(Python)仍能完整完成 Lab,只是少了
3D 高精度驗證那一步。 - Week 15 的 Aviator 發射載具效能模型需自行建立(對照參考檔的 MissileModels 設定),
這步在 STK GUI 或 Aviator API 完成,不在 Connect 範疇。
Week 10 附錄 — 驗證段(補 Astrogator 的回讀死角)
檔案:week10_propulsion/week10_verify.py(共用工具在 tools/stk_verify.py)
為什麼推進週的保證最弱
本週 75 條指令裡有 23 條是結構指令(InsertSegment、AddMCSSegmentControl、
SetMCSControlValue、SetMCSConstraintValue),Connect 對它們沒有任何 GetValue
可以回讀。在加上驗證段之前,整個 MCS 只能靠「最後 delta-V 看起來合理」來擔保。
這一段實際踩過的坑,每一個都是 0 個 NACK:
| 坑 | 症狀 |
|---|---|
裸 - 是「目前插入點」,插了 Target Sequence 之後就跑進它裡面 |
後兩段 InsertSegment 插錯位置,照樣 ACK |
SetProp 會先建一組預設 MCS |
腳本的改名改到預設段,自己插入的變孤兒 |
| 解出的 delta-V 在修正器的控制變數上 | 讀 segment 永遠得到標稱的 0 m/s,沒有任何錯誤 |
DV2 少了 MaxStep / Tolerance |
收斂到遠支解 1946 m/s(遠地點 24900 km),數字荒謬但 ACK |
執行
python week10_propulsion/week10_verify.py # 離開碼 0 = 全部通過
42 項檢查,分五類:
- 自動回讀:24 條
Astrogator ... SetValue逐一比對。 - MCS 結構:主序列的段名與順序、各 Target Sequence 的子段、傳播段的停止條件。
這是補上面那 23 條的死角。 - 修正器:約束名稱/目標值/容差設對了嗎?
Status是不是Converged?
|Difference|有沒有落在Tolerance內? - 物理判準:三次 delta-V 與 Python 解析值比對(容許 10%,涵蓋 HPOP 與兩體的差異,
但抓得到遠支解那種數量級錯誤)。解析值是即時呼叫week10_propulsion_deltav.py
算的,不是抄進來的常數 —— 抄下來的數字會跟著腳本改動而過期。 - 末軌:圓化後偏心率 ≈ 0、deorbit 後近地點命中 6428.137 km。
基準結果:42 項 = 36 PASS / 0 FAIL / 6 SKIP。
6 個 SKIP 是清單型屬性(StoppingConditions / Results),Connect 一律回 Unknown,
已由第 2、3 類的專屬檢查覆蓋。
作業
- 跑一次驗證段,貼上 42 項的輸出,並說明 6 個 SKIP 為什麼可以接受。
- 故意弄壞它,各跑一次並記錄哪幾項變成 FAIL:
- 把Circularize的約束改回"Radius Of Periapsis" Desired 6928.137,並刪掉MaxStep
- 把某一段的InsertSegment MainSequence.SegmentList.-改回裸的-
- 把Deorbit的目標近地點改成 6428.137 → 6000(低於地表) - 第 2 題的第一項會出現
status=Encountered an Error且delta-V = 0.00 m/s。
解釋:為什麼修正器失敗時 delta-V 是 0 而不是某個錯誤的數字?
如果沒有驗證段,你會從哪裡看出不對?
圖

week10_propulsion/week10_deltav.png
程式碼
# ================================================================
# Week 10 主軌 - STK/Astrogator 推進機動(MCS + 差分修正器)
# Python(week10_propulsion_deltav.py)降為量級驗證用。
#
# 任務:CubeSat 從部署軌道(400 km)Hohmann 抬到任務軌道(550 km),
# 任務結束後 deorbit。全部用 Astrogator MCS 精確求解。
#
# 執行:在有 Astrogator 授權的 STK,逐行 root.ExecuteCommand(...)
# 或存成 .txt 用 ConFile 批次載入。
# ================================================================
New / Scenario Week10_Propulsion
SetAnalysisTimePeriod * "1 Jan 2024 00:00:00.000" "2 Jan 2024 00:00:00.000"
SetUnits / km
# ---- 建衛星,設 Astrogator 傳播器 ----
New / */Satellite PropSat
# (SetPropagatorType is not a Connect command - 'Astrogator ... SetProp' below does it)
Astrogator */Satellite/PropSat SetProp
# SetProp creates a default MCS ('Initial State' + 'Propagate').
# Delete both, otherwise the renames below hit the DEFAULT segments and the
# ones this script inserts are left orphaned.
Astrogator */Satellite/PropSat DeleteSegment MainSequence.SegmentList.Initial_State
Astrogator */Satellite/PropSat DeleteSegment MainSequence.SegmentList.Propagate
# ================================================================
# MCS 段 1:初始狀態(部署軌道 400 km 圓軌道, i=56)
# ================================================================
Astrogator */Satellite/PropSat InsertSegment MainSequence.SegmentList.- Initial_State
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Initial_State.ComponentName Deploy
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Deploy.InitialState.Keplerian.PeriapsisRadSize 6778.137 km
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Deploy.InitialState.Keplerian.ApoapsisRadSize 6778.137 km
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Deploy.InitialState.Keplerian.inc 56 deg
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Deploy.InitialState.Keplerian.RAAN 0
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Deploy.InitialState.Keplerian.TA 0
# 設乾質量(供推進劑計算)
# DryMass has no Connect path on this segment (only FuelMass / MaxFuelMass).
# Set it in the GUI, or via the object model:
# sat.Propagator.MainSequence.Item('Deploy').SpacecraftParameters.DryMass = 4.0
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Deploy.FuelMass 0.5 kg
# ================================================================
# MCS 段 2:傳播到近地點(Hohmann burn 1 的施力點)
# ================================================================
Astrogator */Satellite/PropSat InsertSegment MainSequence.SegmentList.- Propagate
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Propagate.ComponentName ToPerigee
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.ToPerigee.Propagator "Earth HPOP Default v10"
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.ToPerigee.StoppingConditions Periapsis
# ================================================================
# MCS 段 3:Target Sequence - Hohmann burn 1
# 差分修正器解 delta-V 使遠地點半徑達 6928 km(550 km)
# ================================================================
Astrogator */Satellite/PropSat InsertSegment MainSequence.SegmentList.- Target_Sequence
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Target_Sequence.ComponentName RaiseApogee
Astrogator */Satellite/PropSat InsertSegment MainSequence.SegmentList.RaiseApogee.SegmentList.- Maneuver
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.RaiseApogee.SegmentList.Maneuver.ComponentName DV1
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.RaiseApogee.SegmentList.DV1.MnvrType Impulsive
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.RaiseApogee.SegmentList.DV1.AttitudeControl "Thrust Vector"
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.RaiseApogee.SegmentList.DV1.ThrustAxes "Satellite VNC(Earth)"
# 設引擎(比衝 Isp,決定推進劑消耗)
# Isp belongs to the engine-model component referenced by DV1.EngineModel
# ('Constant Thrust and Isp'), not to the segment - edit it in the Component
# Browser, or clone the component and point DV1.EngineModel at the clone.
# 控制變數:沿速度方向的 delta-V
Astrogator */Satellite/PropSat AddMCSSegmentControl MainSequence.SegmentList.RaiseApogee.SegmentList.DV1 ImpulsiveMnvr.Cartesian.X
# 結果:遠地點半徑
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.RaiseApogee.SegmentList.DV1.Results "Radius Of Apoapsis"
# 差分修正器設定
Astrogator */Satellite/PropSat SetMCSControlValue MainSequence.SegmentList.RaiseApogee.Profiles.Differential_Corrector DV1 ImpulsiveMnvr.Cartesian.X Active true
Astrogator */Satellite/PropSat SetMCSControlValue MainSequence.SegmentList.RaiseApogee.Profiles.Differential_Corrector DV1 ImpulsiveMnvr.Cartesian.X MaxStep 0.05 km/sec
Astrogator */Satellite/PropSat SetMCSConstraintValue MainSequence.SegmentList.RaiseApogee.Profiles.Differential_Corrector DV1 "Radius Of Apoapsis" Active true
Astrogator */Satellite/PropSat SetMCSConstraintValue MainSequence.SegmentList.RaiseApogee.Profiles.Differential_Corrector DV1 "Radius Of Apoapsis" Desired 6928.137 km
Astrogator */Satellite/PropSat SetMCSConstraintValue MainSequence.SegmentList.RaiseApogee.Profiles.Differential_Corrector DV1 "Radius Of Apoapsis" Tolerance 0.1 km
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.RaiseApogee.Action "Run active profiles"
# ================================================================
# MCS 段 4:傳播到遠地點 + Hohmann burn 2(圓化)
# ================================================================
Astrogator */Satellite/PropSat InsertSegment MainSequence.SegmentList.- Propagate
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Propagate.ComponentName ToApogee
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.ToApogee.StoppingConditions Apoapsis
Astrogator */Satellite/PropSat InsertSegment MainSequence.SegmentList.- Target_Sequence
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Target_Sequence.ComponentName Circularize
Astrogator */Satellite/PropSat InsertSegment MainSequence.SegmentList.Circularize.SegmentList.- Maneuver
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Circularize.SegmentList.Maneuver.ComponentName DV2
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Circularize.SegmentList.DV2.MnvrType Impulsive
Astrogator */Satellite/PropSat AddMCSSegmentControl MainSequence.SegmentList.Circularize.SegmentList.DV2 ImpulsiveMnvr.Cartesian.X
# Circularizing at apogee: "Radius Of Periapsis = 6928 km" is DEGENERATE - a huge
# prograde burn makes the burn point itself the new periapsis and also satisfies it
# (the corrector converged on 1946 m/s with apoapsis ~24900 km). Target eccentricity
# instead; that has a single nearby solution.
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Circularize.SegmentList.DV2.Results "Eccentricity"
Astrogator */Satellite/PropSat SetMCSControlValue MainSequence.SegmentList.Circularize.Profiles.Differential_Corrector DV2 ImpulsiveMnvr.Cartesian.X Active true
Astrogator */Satellite/PropSat SetMCSControlValue MainSequence.SegmentList.Circularize.Profiles.Differential_Corrector DV2 ImpulsiveMnvr.Cartesian.X MaxStep 0.05 km/sec
Astrogator */Satellite/PropSat SetMCSConstraintValue MainSequence.SegmentList.Circularize.Profiles.Differential_Corrector DV2 Eccentricity Active true
Astrogator */Satellite/PropSat SetMCSConstraintValue MainSequence.SegmentList.Circularize.Profiles.Differential_Corrector DV2 Eccentricity Desired 0.0
Astrogator */Satellite/PropSat SetMCSConstraintValue MainSequence.SegmentList.Circularize.Profiles.Differential_Corrector DV2 Eccentricity Tolerance 0.0001
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Circularize.Action "Run active profiles"
# ================================================================
# 執行 MCS 並讀結果
# ================================================================
# ================================================================
# MCS 段 6:任務結束後 deorbit(降近地點至再入高度)
# 對照 Python 的 deorbit_dv(r_perigee_target = RE + 50 km) = 143.3 m/s。
# 在遠地點逆向燒可把近地點拉下來 —— 這是 61% 的任務 delta-V,
# 先前的 MCS 沒有這一段,所以 Python 的 233 m/s 有大半無從對照。
# ================================================================
Astrogator */Satellite/PropSat InsertSegment MainSequence.SegmentList.- Propagate
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Propagate.ComponentName Coast2Deorbit
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Coast2Deorbit.StoppingConditions Apoapsis
Astrogator */Satellite/PropSat InsertSegment MainSequence.SegmentList.- Target_Sequence
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Target_Sequence.ComponentName Deorbit
Astrogator */Satellite/PropSat InsertSegment MainSequence.SegmentList.Deorbit.SegmentList.- Maneuver
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Deorbit.SegmentList.Maneuver.ComponentName DV3
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Deorbit.SegmentList.DV3.MnvrType Impulsive
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Deorbit.SegmentList.DV3.AttitudeControl "Thrust Vector"
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Deorbit.SegmentList.DV3.ThrustAxes "Satellite VNC(Earth)"
# 控制變數:沿速度方向的 delta-V(解出來會是負值 = 逆向燒)
Astrogator */Satellite/PropSat AddMCSSegmentControl MainSequence.SegmentList.Deorbit.SegmentList.DV3 ImpulsiveMnvr.Cartesian.X
# 目標:近地點半徑 = 6378.137 + 50 = 6428.137 km
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Deorbit.SegmentList.DV3.Results "Radius Of Periapsis"
Astrogator */Satellite/PropSat SetMCSControlValue MainSequence.SegmentList.Deorbit.Profiles.Differential_Corrector DV3 ImpulsiveMnvr.Cartesian.X Active true
Astrogator */Satellite/PropSat SetMCSControlValue MainSequence.SegmentList.Deorbit.Profiles.Differential_Corrector DV3 ImpulsiveMnvr.Cartesian.X MaxStep 0.05 km/sec
Astrogator */Satellite/PropSat SetMCSConstraintValue MainSequence.SegmentList.Deorbit.Profiles.Differential_Corrector DV3 "Radius Of Periapsis" Active true
Astrogator */Satellite/PropSat SetMCSConstraintValue MainSequence.SegmentList.Deorbit.Profiles.Differential_Corrector DV3 "Radius Of Periapsis" Desired 6428.137 km
Astrogator */Satellite/PropSat SetMCSConstraintValue MainSequence.SegmentList.Deorbit.Profiles.Differential_Corrector DV3 "Radius Of Periapsis" Tolerance 1.0 km
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Deorbit.Action "Run active profiles"
Astrogator */Satellite/PropSat RunMCS
# 讀三次 burn 的 delta-V(對照 Python:41.8 / 41.6 / -143.3 m/s)
Astrogator_RM */Satellite/PropSat GetMCSControlValue MainSequence.SegmentList.RaiseApogee.Profiles.Differential_Corrector DV1 ImpulsiveMnvr.Cartesian.X FinalValue
Astrogator_RM */Satellite/PropSat GetMCSControlValue MainSequence.SegmentList.Circularize.Profiles.Differential_Corrector DV2 ImpulsiveMnvr.Cartesian.X FinalValue
Astrogator_RM */Satellite/PropSat GetMCSControlValue MainSequence.SegmentList.Deorbit.Profiles.Differential_Corrector DV3 ImpulsiveMnvr.Cartesian.X FinalValue
# 讀消耗的推進劑質量與末軌(近地點應落在 6428 km 附近)
Astrogator_RM */Satellite/PropSat GetValue MainSequence.SegmentList.Deorbit.FinalState.FuelMass
Astrogator_RM */Satellite/PropSat GetValue MainSequence.SegmentList.Deorbit.FinalState.Keplerian.PeriapsisRadSize
# ---- 出報告驗證軌道 ----
ReportCreate */Satellite/PropSat Type Save Style "Classical Orbit Elements" File "C:\STK_Reports\propsat_elements.txt"
# -*- coding: utf-8 -*-
# ================================================================
# Week 10 Lab - 推進系統:delta-V 預算與機動
#
# 雙軌(同 Week 5/8 哲學):
# (A) Python 解析軌道力學 = 保證可跑:Hohmann 轉移、軌道維持、
# deorbit 的 delta-V,配合火箭方程算推進劑質量。
# (B) STK/Astrogator = 正牌高精度:MCS 任務控制序列 + 差分修正器
# 求解機動(見檔尾 astrogator_mcs_connect,對照 TASA/官方範例)。
#
# 輸出:week10_deltav.png + 主控台 delta-V/推進劑預算
# ================================================================
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import os
# 產出要落在**週次目錄**,不是 process 的工作目錄。每一支 weekNN_verify.py
# 開頭都有這個慣用法並註明「啟動 STK Engine 會改變 process 的工作目錄」,
# 但 lab 這一側先前沒有套用 —— 結果庫根目錄散落著九個產出,
# 其中一份 week15_launch.png 還是過期的。notebook 的 kernel 沒有 __file__,
# 所以留 WEEK_DIR(notebook 開頭的 bootstrap cell 會設)當後備。
HERE = (os.path.dirname(os.path.abspath(__file__)) if "__file__" in globals()
else globals().get("WEEK_DIR") or os.path.abspath(os.getcwd()))
MU = 398600.4418 # km^3/s^2
RE = 6378.137 # km
G0 = 9.80665 # m/s^2
def v_circ(r): return np.sqrt(MU/r)
def hohmann(r1, r2):
"""兩圓軌道間 Hohmann 轉移的兩次 delta-V(km/s)與轉移時間(s)。"""
at = 0.5*(r1+r2)
v1 = v_circ(r1); v2 = v_circ(r2)
vp = np.sqrt(MU*(2/r1 - 1/at)) # 轉移橢圓近地點速度
va = np.sqrt(MU*(2/r2 - 1/at)) # 轉移橢圓遠地點速度
dv1 = abs(vp - v1); dv2 = abs(v2 - va)
tof = np.pi*np.sqrt(at**3/MU)
return dv1, dv2, dv1+dv2, tof
def deorbit_dv(r_circ, r_perigee_target=RE+50):
"""從圓軌道降近地點到再入高度(~50 km)的單次 delta-V。"""
at = 0.5*(r_circ + r_perigee_target)
v_c = v_circ(r_circ)
v_a = np.sqrt(MU*(2/r_circ - 1/at)) # 降軌橢圓在原高度(遠地點)的速度
return abs(v_c - v_a)
# ---- 大氣密度:Vallado 的指數大氣表(公開文獻,不是本教材擬合的)----
# 《Fundamentals of Astrodynamics and Applications》的分段指數模型。
# 每一段 (基準高度 km, 基準密度 kg/m^3, 標高 km)。
ATMOS = [
(0, 1.225, 7.249), (25, 3.899e-2, 6.349), (30, 1.774e-2, 6.682),
(40, 3.972e-3, 7.554), (50, 1.057e-3, 8.382), (60, 3.206e-4, 7.714),
(70, 8.770e-5, 6.549), (80, 1.905e-5, 5.799), (90, 3.396e-6, 5.382),
(100, 5.297e-7, 5.877), (110, 9.661e-8, 7.263), (120, 2.438e-8, 9.473),
(130, 8.484e-9, 12.636), (140, 3.845e-9, 16.149), (150, 2.070e-9, 22.523),
(180, 5.464e-10, 29.740), (200, 2.789e-10, 37.105), (250, 7.248e-11, 45.546),
(300, 2.418e-11, 53.628), (350, 9.518e-12, 53.298), (400, 3.725e-12, 58.515),
(450, 1.585e-12, 60.828), (500, 6.967e-13, 63.822), (600, 1.454e-13, 71.835),
(700, 3.614e-14, 88.667), (800, 1.170e-14, 124.64), (900, 5.245e-15, 181.05),
(1000, 3.019e-15, 268.00),
]
YEAR_S = 365.25*86400.0
# 3U CubeSat 的彈道係數。面積不是猜的:**凸體隨機翻滾的平均投影面積 = 表面積 / 4**
# (Cauchy 的公式)。3U 是 10x10x30 cm,表面積 0.14 m^2 -> 平均投影 0.035 m^2。
# 乾重 4 kg(與下面 main() 的 m_dry 一致),阻力係數取自由分子流的典型值 2.2。
CUBESAT_3U_AREA = 0.14/4.0 # m^2,平均投影面積
CUBESAT_CD_A_M = 2.2*CUBESAT_3U_AREA/4.0 # m^2/kg
def atmos_density(alt_km):
"""指數大氣的密度(kg/m^3)。"""
for h0, rho0, H in reversed(ATMOS):
if alt_km >= h0:
return rho0*np.exp(-(alt_km - h0)/H)
return ATMOS[0][1]
def drag_makeup_dv_per_year(alt_km, cd_a_over_m=CUBESAT_CD_A_M):
"""圓軌道的年阻力補償 delta-V(km/s/yr)。
連續推力剛好抵銷阻力,所以 Δv = 阻力加速度 x 時間:
a_D = 0.5 rho v^2 (Cd A / m)
Δv/yr = a_D * 一年的秒數
**舊版是 `0.05*exp(-(alt-300)/120)`,那條曲線是編的**,有三個問題:
1. 它的 docstring 說「~500 km 幾十 m/s/yr、~300 km 上百」,
而公式自己給 9.4 與 50 —— **說法與公式對不上**。
2. **它沒有彈道係數。** 阻力 Δv 正比於 Cd A / m,而那是衛星之間差最多的
參數:同樣在 400 km,1U 與 3U 差 3 倍,對上 ISS 級的差 15 倍。
一條沒有這個參數的曲線,不可能同時對兩種衛星。
3. 它的標高 120 km 比真實大氣平緩太多(300-500 km 實際是 53-64 km),
所以低空低估、高空高估。
改用物理式之後可以拿外部數字驗:ISS(Cd A/m 約 0.0105 m^2/kg、
高度約 415 km)算出約 28 m/s/yr,與公開常引用的
「每年約 25-40 m/s(隨太陽活動變動)」同一量級。
"""
v = np.sqrt(MU/(RE + alt_km))*1000.0 # m/s
a_drag = 0.5*atmos_density(alt_km)*v*v*cd_a_over_m
return a_drag*YEAR_S/1000.0 # km/s per year
def prop_mass(dv_total_ms, m_dry, isp):
"""火箭方程:給總 delta-V(m/s)、乾質量、比衝,回推進劑質量(kg)。"""
return m_dry*(np.exp(dv_total_ms/(isp*G0)) - 1)
def main():
# --- 任務情境:CubeSat 從部署軌道抬到任務軌道,維持一年,再 deorbit ---
alt_deploy = 400.0 # km,rideshare 部署高度
alt_mission = 550.0 # km,任務軌道
mission_years = 1.0
m_dry = 4.0 # kg(3U + 推進模組乾重)
ISP = 220.0 # s(冷氣/單推系統典型;電推可到 >1000)
r1, r2 = RE+alt_deploy, RE+alt_mission
dv1, dv2, dv_hoh, tof = hohmann(r1, r2)
dv_sk = drag_makeup_dv_per_year(alt_mission)*mission_years
dv_de = deorbit_dv(r2)
dv_total = dv_hoh + dv_sk + dv_de # km/s
dv_total_ms = dv_total*1000
mp = prop_mass(dv_total_ms, m_dry, ISP)
print("=== delta-V 預算 (CubeSat 推進任務) ===")
print(f"部署 {alt_deploy:.0f} km -> 任務 {alt_mission:.0f} km (Hohmann)")
print(f" burn 1 (近地點): {dv1*1000:6.1f} m/s")
print(f" burn 2 (遠地點): {dv2*1000:6.1f} m/s")
print(f" 轉移時間 : {tof/60:5.1f} min")
print(f"軌道維持 (阻力補償, {mission_years:.0f} yr): {dv_sk*1000:6.1f} m/s")
print(f"deorbit (降近地點至再入) : {dv_de*1000:6.1f} m/s")
print(f"--- 總 delta-V: {dv_total_ms:6.1f} m/s ---")
print(f"\n火箭方程 (Isp={ISP:.0f}s, 乾重 {m_dry:.1f}kg):")
print(f" 需推進劑: {mp*1000:.1f} g ({100*mp/(m_dry+mp):.1f}% of wet mass)")
# --- 圖 ---
fig, ax = plt.subplots(1, 3, figsize=(14, 4.5))
# (1) delta-V 分項堆疊
items = ["Hohmann\nup", "Station-\nkeeping", "Deorbit"]
vals = [dv_hoh*1000, dv_sk*1000, dv_de*1000]
colors = ["tab:blue", "tab:orange", "tab:red"]
bottom = 0
for it, v, c in zip(items, vals, colors):
ax[0].bar(0, v, bottom=bottom, color=c, label=f"{it.strip()}: {v:.0f}")
bottom += v
ax[0].set_xticks([]); ax[0].set_ylabel("delta-V (m/s)")
ax[0].set_title(f"delta-V budget (total {dv_total_ms:.0f} m/s)")
ax[0].legend(fontsize=8)
# (2) 推進劑質量 vs Isp(不同推進技術)
isps = np.linspace(60, 1500, 200)
mps = [prop_mass(dv_total_ms, m_dry, isp)*1000 for isp in isps]
ax[1].plot(isps, mps, color="tab:green", lw=2)
for isp_lbl, name in [(70,"cold gas"),(220,"monoprop"),(300,"biprop"),(1200,"電推 EP")]:
ax[1].axvline(isp_lbl, color="gray", ls=":", lw=0.8)
ax[1].annotate(name,(isp_lbl,ax[1].get_ylim()[1]*0.9),fontsize=7,rotation=90,va="top")
ax[1].set_xlabel("Isp (s)"); ax[1].set_ylabel("propellant mass (g)")
ax[1].set_title(f"Propellant vs Isp (dry {m_dry} kg)")
ax[1].grid(alpha=0.3)
# (3) Hohmann 轉移軌道示意
th = np.linspace(0, 2*np.pi, 200)
ax[2].plot(r1*np.cos(th), r1*np.sin(th), "b--", lw=1, label=f"deploy {alt_deploy:.0f}km")
ax[2].plot(r2*np.cos(th), r2*np.sin(th), "r--", lw=1, label=f"mission {alt_mission:.0f}km")
at = 0.5*(r1+r2); e = (r2-r1)/(r2+r1); b = at*np.sqrt(1-e**2)
ax[2].plot(at*np.cos(th)-at*e, b*np.sin(th), "g-", lw=1.8, label="transfer")
ax[2].add_patch(plt.Circle((0,0), RE, color="steelblue", alpha=0.5))
ax[2].set_aspect("equal"); ax[2].set_title("Hohmann transfer"); ax[2].legend(fontsize=7)
ax[2].set_xticks([]); ax[2].set_yticks([])
fig.suptitle("Week 10 - Propulsion: delta-V budget & maneuvers", fontsize=13)
fig.tight_layout(rect=[0,0,1,0.95])
fig.savefig(os.path.join(HERE, "week10_deltav.png"), dpi=130)
print("\nWrote week10_deltav.png")
# ================================================================
# STK / Astrogator 正牌路徑(Connect 指令,對照官方 MATLAB snippet)
# 在有 Astrogator 授權的 STK 執行:root.ExecuteCommand(每一行)
# ================================================================
ASTROGATOR_MCS_CONNECT = r'''
# 設定衛星用 Astrogator 傳播,清空 MCS
Astrogator */Satellite/PropSat SetProp
# 初始狀態段
Astrogator */Satellite/PropSat InsertSegment - Initial_State
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Initial_State.InitialState.Keplerian.PeriapsisRadSize 6778 km
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Initial_State.InitialState.Keplerian.ecc 0
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Initial_State.InitialState.Keplerian.inc 56
# 傳播到近地點
Astrogator */Satellite/PropSat InsertSegment - Propagate
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Propagate.StoppingConditions Periapsis
# 機動段(Hohmann burn 1),用差分修正器求 delta-V 讓遠地點達目標
Astrogator */Satellite/PropSat InsertSegment - Target_Sequence
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Target_Sequence.ComponentName Raise
Astrogator */Satellite/PropSat InsertSegment MainSequence.SegmentList.Raise.SegmentList.- Maneuver
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Raise.SegmentList.Maneuver.ComponentName DV1
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Raise.SegmentList.DV1.MnvrType Impulsive
Astrogator */Satellite/PropSat AddMCSSegmentControl MainSequence.SegmentList.Raise.SegmentList.DV1 ImpulsiveMnvr.Cartesian.X
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Raise.SegmentList.DV1.Results "Radius Of Apoapsis"
Astrogator */Satellite/PropSat SetMCSControlValue MainSequence.SegmentList.Raise.Profiles.Differential_Corrector DV1 ImpulsiveMnvr.Cartesian.X Active true
Astrogator */Satellite/PropSat SetMCSConstraintValue MainSequence.SegmentList.Raise.Profiles.Differential_Corrector DV1 "Radius Of Apoapsis" Desired 6928 km
Astrogator */Satellite/PropSat SetValue MainSequence.SegmentList.Raise.Action "Run active profiles"
# 執行 MCS
Astrogator */Satellite/PropSat RunMCS
# 讀 delta-V 結果
Astrogator_RM */Satellite/PropSat GetValue MainSequence.SegmentList.Raise.SegmentList.DV1.ImpulsiveMnvr.Cartesian.X
'''
if __name__ == "__main__":
main()
# -*- coding: utf-8 -*-
# ================================================================
# Week 10 驗證段 — 證明 MCS 真的長成腳本描述的樣子,而且修正器收斂了
#
# 為什麼推進週特別需要這一段:
# 本週 75 條指令裡有 23 條是**結構指令**(InsertSegment、
# AddMCSSegmentControl、SetMCSControlValue、SetMCSConstraintValue),
# Connect 對它們沒有任何 GetValue 可以回讀。全教材就屬這一段的
# 保證最弱 —— 先前只能靠「最後 delta-V 看起來合理」來擔保。
#
# 而這一段實際踩過的坑,每一個都是 0 個 NACK:
# - 裸 "-" 是「目前插入點」,插了 Target Sequence 之後就跑進它裡面,
# 後面兩段 InsertSegment 全插錯地方,Connect 照樣 ACK
# - SetProp 會先建一組預設 MCS,腳本的改名其實改到預設段,
# 自己插入的段變成孤兒
# - 解出的 delta-V 在**修正器的控制變數**上,segment 上永遠是標稱的 0,
# 讀錯地方會得到 0 m/s 卻毫無錯誤訊息
# - DV2 少了 MaxStep/Tolerance,修正器收斂到「近地點 6928 km」的
# 遠支解(1946 m/s、遠地點 24900 km),數字荒謬但一樣 ACK
#
# 執行:
# 1. 開啟 STK 12 桌面版(不能用無介面的 STK Engine)
# 2. python week10_verify.py
# 離開碼 0 = 全部通過,1 = 有項目沒生效
# ================================================================
import os
import sys
# notebook 的 kernel 沒有 __file__。而且啟動 STK Engine 會改變 process 的
# 工作目錄,所以不能事後才靠 os.getcwd() —— notebook 開頭的 bootstrap cell
# 會先把週次目錄釘在 WEEK_DIR。
HERE = (os.path.dirname(os.path.abspath(__file__)) if "__file__" in globals()
else globals().get("WEEK_DIR") or os.path.abspath(os.getcwd()))
REPO = os.path.abspath(os.path.join(HERE, os.pardir))
sys.path.insert(0, os.path.join(REPO, "tools"))
sys.path.insert(0, HERE)
from agi.stk12.stkdesktop import STKDesktop # noqa: E402
from stk_verify import Verifier, run_script # noqa: E402
SCRIPT = os.path.join(HERE, "week10_propulsion_astrogator.txt")
SAT = "*/Satellite/PropSat"
# 腳本描述的 MCS,順序不能錯
EXPECTED_MCS = ["Deploy", "ToPerigee", "RaiseApogee", "ToApogee",
"Circularize", "Coast2Deorbit", "Deorbit"]
RE = 6378.137
R_DEPLOY, R_MISSION = RE + 400.0, RE + 550.0
R_REENTRY = RE + 50.0
def analytic():
"""直接呼叫 Week 10 的 Python 解析腳本,不要抄數字進來 ——
抄下來的常數會跟著腳本改動而過期。"""
import importlib.util
spec = importlib.util.spec_from_file_location(
"w10dv", os.path.join(HERE, "week10_propulsion_deltav.py"))
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m) # main() 有 __main__ 保護,不會執行
dv1, dv2, _, _ = m.hohmann(R_DEPLOY, R_MISSION)
dv3 = m.deorbit_dv(R_MISSION, R_REENTRY)
return dv1 * 1000, dv2 * 1000, dv3 * 1000
def main():
app = STKDesktop.AttachToApplication() # 保留 app 參考,否則 COM 介面會失效
root = app.Root
if root.CurrentScenario is not None:
root.CloseScenario()
ack, nack = run_script(root, SCRIPT)
a1, a2, a3 = analytic()
print(f"Python 解析參考值:{a1:.1f} / {a2:.1f} / {a3:.1f} m/s")
v = Verifier(root)
# (1) 自動:腳本設過的每個 Astrogator SetValue
v.auto_setvalues(SCRIPT)
# (2) MCS 結構 —— 補 Connect 完全沒有回讀路徑的那 23 條
v.propagator_is(SAT, "Astrogator")
v.mcs_sequence(SAT, EXPECTED_MCS)
v.mcs_subsegments(SAT, "RaiseApogee", ["DV1"])
v.mcs_subsegments(SAT, "Circularize", ["DV2"])
v.mcs_subsegments(SAT, "Deorbit", ["DV3"])
v.stopping_condition(SAT, "ToPerigee", "Periapsis")
v.stopping_condition(SAT, "ToApogee", "Apoapsis")
v.stopping_condition(SAT, "Coast2Deorbit", "Apoapsis")
# (3) 修正器:約束設對了嗎?真的收斂了嗎?
v.dc_constraint(SAT, "RaiseApogee", "Radius Of Apoapsis", 6928.137, 0.1)
v.dc_constraint(SAT, "Circularize", "Eccentricity", 0.0, 0.0001)
v.dc_constraint(SAT, "Deorbit", "Radius Of Periapsis", 6428.137, 1.0)
for ts in ("RaiseApogee", "Circularize", "Deorbit"):
v.dc_converged(SAT, ts)
# (4) 物理判準:解出的 delta-V 對得上解析值嗎?
# 容許 10% —— STK 用 HPOP 含攝動,Python 用理想兩體,本來就會差幾 %。
# 容許範圍要「夠鬆到不會誤報、夠緊到抓得到遠支解(1946 m/s)」。
v.delta_v(SAT, "RaiseApogee", a1, rel_tol=0.10)
v.delta_v(SAT, "Circularize", a2, rel_tol=0.10)
v.delta_v(SAT, "Deorbit", a3, rel_tol=0.10)
# (5) 末軌:圓化到位了嗎?近地點降到再入高度了嗎?
v.final_element(SAT, "Circularize", "ecc", 0.0, 1e-3) # 根數名是 ecc,不是 Eccentricity
v.final_element(SAT, "Deorbit", "PeriapsisRadSize", 6428.137, 1.0)
# ---- 阻力補償:拿**外部數字**驗,不是拿模型對自己 ----
# 舊版的 drag_makeup_dv_per_year 是一條編出來的指數
# (`0.05*exp(-(alt-300)/120)`),而且沒有彈道係數 ——
# 阻力 Δv 正比於 Cd A / m,那是衛星之間差最多的參數。
# 改成物理式之後就驗得動了。
m = load_lab(os.path.join(HERE, "week10_propulsion_deltav.py"))
# (1) ISS:公開常引用「每年約 25-40 m/s(隨太陽活動變動)」
iss_cd_a_m = 2.2*2000.0/420000.0 # Cd 2.2、迎風面積約 2000 m^2、420 t
v.expect_between("ISS 的年阻力補償(公開約 25-40 m/s/yr)",
m.drag_makeup_dv_per_year(415.0, iss_cd_a_m)*1000.0,
25.0, 40.0, unit=" m/s/yr")
# (2) 大氣密度要對得上 Vallado 表的基準點(那是文獻值,不是本檔算的)
for h, rho_ref in ((200, 2.789e-10), (400, 3.725e-12), (800, 1.170e-14)):
got = m.atmos_density(float(h))
v.expect_true("大氣密度 @ %d km 對上文獻基準點" % h,
abs(got - rho_ref)/rho_ref < 1e-9,
"%.4e vs %.4e kg/m^3" % (got, rho_ref))
# (3) 正比於彈道係數 —— 這是舊公式**表達不了**的東西
base = m.drag_makeup_dv_per_year(400.0, 0.01)
v.expect("Δv 正比於 Cd·A/m(加倍就要加倍)",
m.drag_makeup_dv_per_year(400.0, 0.02), 2.0*base, tol=1e-12,
unit=" km/s/yr")
# (4) 隨高度單調下降,而且比舊的編出來的曲線陡
alts = [300.0, 400.0, 500.0, 600.0, 700.0, 800.0]
dvs = [m.drag_makeup_dv_per_year(a) for a in alts]
v.expect_true("年阻力 Δv 隨高度單調下降",
all(dvs[i] > dvs[i+1] for i in range(len(dvs)-1)),
"%.1f -> %.2f m/s/yr" % (dvs[0]*1000, dvs[-1]*1000))
# 300 -> 800 km 真實大氣要掉三個數量級以上;舊公式只掉 60 倍
v.expect_true("300 -> 800 km 至少掉三個數量級(舊的編出來的只掉 60 倍)",
dvs[0]/dvs[-1] > 1e3,
"掉 %.0f 倍" % (dvs[0]/dvs[-1]))
ok = v.report("Week 10 驗證段")
print(f"\n對照:腳本 {ack} 條 ACK / {nack} 條 NACK —— "
f"其中 23 條結構指令沒有任何 GetValue 可回讀,")
print(" 上面的 MCS 結構與修正器收斂檢查就是補這個死角。")
return 0 if ok else 1
if __name__ == "__main__":
_rc = main()
# 在 notebook 裡不要用 SystemExit 打斷 kernel;在終端機才回傳離開碼
if "ipykernel" not in sys.modules:
raise SystemExit(_rc)