diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc7c631 --- /dev/null +++ b/README.md @@ -0,0 +1,357 @@ +# Moon Colony Logistics(地月运输建模与对比) + +本目录包含针对 **MCM 2026 Problem B**(见 `prob/en_B.md`)的一组可运行脚本:用**简化的轨道力学 + 火箭方程**估算把 **1 亿吨(100 million metric tons)**物资送到月球轨道/基地的**时间线**与**能量消耗**,并对比: + +- **纯火箭方案**(10 个既有发射场,每站每日 1 发) +- **纯太空电梯方案**(3 个电梯港口持续转运) +- **混合方案**(电梯优先,剩余由火箭补齐,且随着工期变长更多任务转移到低纬发射场以降低纬度惩罚) + +> 本项目的“能量”口径: +> - **火箭**:用推进剂化学能近似 \(E \approx m_\text{fuel}\cdot e_s\)(`e_s` 为燃料比能,默认 LH2/LOX 约 15.5 MJ/kg)。 +> - **太空电梯**:用“提升能 + 顶端火箭段能量”的**总比能量**近似(默认 157.2 MJ/kg,即 157.2 GJ/吨)。 +> 这是一种用于**对比量级与趋势**的工程近似,不等同于严格的热力学效率或完整任务能量收支。 + +--- + +### 目录结构与脚本索引 + +#### 核心脚本 + +- **`specific_energy_comparison.py`** + 地面发射 vs 太空电梯:比能量、燃料比、纬度影响(包含指定发射场列表)。 +- **`rocket_launch_scenario.py`** + 纯火箭方案(10 发射场 * 每日 1 发)排布与能量-工期曲线;并做发射频率敏感性分析。 +- **`combined_scenario.py`** + 混合方案(电梯优先 + 火箭补齐),输出组合方案能量-工期曲线,并生成 **100–300 年**区间三方案同图对比。 +- **`launch_capacity_analysis.py`** + (可选)对历史火箭年发射量做 Logistic / Gompertz / Richards 拟合与预测,生成 `launch_capacity_*.png` 与 `launch_capacity_results.csv`。 + +#### 主要输出图表(均在本目录) + +| 图表文件 | 含义 | +|---|---| +| `specific_energy_comparison.png` | 地面 vs 电梯的比能量/燃料比对比,含 ΔV 敏感性 | +| `latitude_effects.png` | 月球任务场景下:纬度→自转速度损失→燃料/能量增加趋势(并标注指定发射场) | +| `rocket_launch_timeline.png` | 纯火箭方案:完成年限 vs 总能量(工期变长→更多低纬→能量略降) | +| `rocket_comprehensive.png` | 纯火箭方案综合图:能量趋势 + 分配饼图等 | +| `launch_frequency_analysis.png` | 发射频率敏感性:每站每日发射数提升→完工年限显著下降 | +| `combined_scenario_analysis.png` | 混合方案:总能量/电梯能量/火箭能量随工期变化 + 电梯占比 + 需要启用的火箭站点数 | +| `scenario_comparison.png` | elevator-only / rocket-only / combined 的完工时间与能量柱状对比 | +| **`three_scenarios_comparison.png`** | **100–300 年区间**:纯火箭、纯电梯、混合方案的“年份-能量”折线同图对比 | +| `launch_capacity_analysis.png` `launch_capacity_physical.png` `launch_capacity_predictions.png` | (可选)火箭发射能力上限/趋势拟合与预测 | + +#### 数据文件(如存在) + +- `launch_capacity_results.csv` +- `rocket_launch_counts.csv` +- `launch_sites.csv` +- `rocket launch counts.txt` + +--- + +### 图表总览(本目录所有 PNG) + +为便于写报告/插图,这里把当前目录下的所有 PNG 统一列出(按主题分组)。如果你只想看关键结论,优先看 **`three_scenarios_comparison.png`**。 + +
+1) 比能量与纬度效应(地面 vs 电梯、发射场纬度) + +#### `specific_energy_comparison.png` + +![specific_energy_comparison](specific_energy_comparison.png) + +#### `latitude_effects.png` + +![latitude_effects](latitude_effects.png) + +
+ +
+2) 纯火箭方案(排布与敏感性) + +#### `rocket_launch_timeline.png` + +![rocket_launch_timeline](rocket_launch_timeline.png) + +#### `rocket_comprehensive.png` + +![rocket_comprehensive](rocket_comprehensive.png) + +#### `launch_distribution_min.png` + +![launch_distribution_min](launch_distribution_min.png) + +#### `launch_distribution_mid.png` + +![launch_distribution_mid](launch_distribution_mid.png) + +#### `launch_frequency_analysis.png` + +![launch_frequency_analysis](launch_frequency_analysis.png) + +
+ +
+3) 混合方案与三方案对比(核心结论图) + +#### `combined_scenario_analysis.png` + +![combined_scenario_analysis](combined_scenario_analysis.png) + +#### `scenario_comparison.png` + +![scenario_comparison](scenario_comparison.png) + +#### `three_scenarios_comparison.png`(100–300 年折线同图对比) + +![three_scenarios_comparison](three_scenarios_comparison.png) + +
+ +
+4)(可选)火箭发射能力上限/趋势拟合 + +#### `launch_capacity_analysis.png` + +![launch_capacity_analysis](launch_capacity_analysis.png) + +#### `launch_capacity_physical.png` + +![launch_capacity_physical](launch_capacity_physical.png) + +#### `launch_capacity_predictions.png` + +![launch_capacity_predictions](launch_capacity_predictions.png) + +
+ +
+5) 其它历史遗留图 + +本目录还包含 `earth_moon_transfer_analysis.png`(早期“地月转移:载荷–燃料/能量关系”图)。当前目录下未发现对应的 `earth_moon_transfer.py` 源码文件;如果你需要我把该脚本补回并与现有口径统一,我可以再加一个文件。 + +#### `earth_moon_transfer_analysis.png` + +![earth_moon_transfer_analysis](earth_moon_transfer_analysis.png) + +
+ +### 依赖安装 + +建议使用 Python 3.10+。 + +```bash +pip install numpy matplotlib pandas +``` + +如需运行 `launch_capacity_analysis.py`(含曲线拟合),还需要: + +```bash +pip install scipy +``` + +--- + +### 数学模型(简化版)概览 + +#### 1)火箭方程:燃料-载荷的线性比例(在固定 ΔV 与 Isp 下) + +火箭方程: +\[ +\Delta V = v_e \ln\left(\frac{m_0}{m_f}\right),\quad v_e = I_{sp} g_0 +\] + +当任务 ΔV、发动机 \(I_{sp}\) 固定时,质量比 \(R=\exp(\Delta V/v_e)\) 为常数。若进一步采用结构系数近似: +\[ +m_s=\alpha m_\text{fuel} +\] +则可解得燃料与载荷满足: +\[ +\boxed{m_\text{fuel}=k\,m_p}\quad(\text{其中 }k\text{ 为常数,取决于 }\Delta V, I_{sp}, \alpha) +\] + +这解释了为什么在同一任务与发动机参数下,“载荷–燃料”是近似**线性**关系。 + +#### 2)能量口径(用于对比) + +- 火箭推进剂能量(近似): +\[ +E_\text{rocket}\approx m_\text{fuel}\,e_s +\] +其中 \(e_s\) 为燃料比能(默认 15.5 MJ/kg)。 + +- 太空电梯总比能量(本项目用常数近似): +\[ +E_\text{elevator}\approx \epsilon_\text{elevator}\,m_p +\] +默认 \(\epsilon_\text{elevator}=157.2\text{ MJ/kg}\)(可在脚本中改)。 + +#### 3)纬度导致的自转“损失”近似 + +地球表面自转线速度: +\[ +v(\varphi)=\omega R_E\cos(\varphi) +\] +相对赤道的速度差近似为 ΔV 惩罚: +\[ +\Delta v_\text{loss}=v(0^\circ)-v(\varphi) +\] + +在月球任务场景下(允许进入与发射场纬度相匹配的倾角、避免昂贵的纯平面变轨),纬度主要通过该项影响火箭能耗。 + +--- + +### 如何运行(会生成图片) + +#### 1)比能量与纬度效应 + +```bash +python specific_energy_comparison.py +``` + +输出(示例)会包括不同发射场的 ΔV 损失、燃料比、能量(MJ/kg)等,并生成: + +- `specific_energy_comparison.png` +- `latitude_effects.png` + +#### 2)纯火箭方案:工期-能量与排布 + +```bash +python rocket_launch_scenario.py +``` + +会生成: + +- `rocket_launch_timeline.png` +- `rocket_comprehensive.png` +- `launch_distribution_min.png`、`launch_distribution_mid.png` +- `launch_frequency_analysis.png` + +**部分运行输出(示例)**: + +```text +Total payload: 100 million metric tons +Payload per launch: 125 metric tons +Total launches needed: 800,000 +Number of launch sites: 10 +Minimum completion time: 219.2 years + +Energy reduction (Extended vs Minimum time): 2.4% +``` + +解释: +- 由于“每站每日 1 发、载荷 125 吨”的硬约束,纯火箭最短也要约 **219 年**完成。 +- 工期拉长后,可把更多发射集中到低纬站点,**能量仅小幅下降(约几个百分点)**——纬度惩罚相对火箭整体能耗并不占主导。 + +#### 3)混合方案 + 100–300 年三方案同图对比 + +```bash +python combined_scenario.py +``` + +会生成: + +- `combined_scenario_analysis.png` +- `scenario_comparison.png` +- **`three_scenarios_comparison.png`(100–300 年折线同图对比)** + +**部分运行输出(示例)**: + +```text +Elevator-only completion: 186.2 years +Minimum completion (all systems): 100.7 years + +1. Minimum Time (~101 years): Total energy: 31537 PJ +4. Elevator Only (186 years): Total energy: 15720 PJ +Rocket Only (219 years): Total energy: 50609 PJ +``` + +解释: +- **混合方案**把最短工期从电梯单独的 ~186 年压缩到 ~101 年(靠火箭补齐),但能量高于纯电梯。 +- 当工期 ≥ 186 年时,电梯即可独立完成,混合方案会自动退化为纯电梯曲线(能耗趋于同一水平)。 +- 纯火箭能耗显著更高(本模型下约为纯电梯的 **3×** 量级)。 + +--- + +### 发射场(10 个)与纬度效应(月球任务场景) + +本项目按纬度从低到高优先分配火箭发射(“低纬优先”贪心策略),站点列表与纬度如下(与题目一致): + +| 发射场 | 近似纬度 | 备注 | +|---|---:|---| +| French Guiana(Kourou) | 5.2° | 低纬优势明显 | +| Satish Dhawan(India) | 13.7° | 低纬 | +| Texas(Boca Chica) | 26.0° | 美国 | +| Florida(Cape Canaveral) | 28.5° | 美国 | +| California(Vandenberg) | 34.7° | 美国 | +| Virginia(Wallops) | 37.8° | 美国 | +| Taiyuan(China) | 38.8° | 中国 | +| Mahia(New Zealand) | 39.3° | 新西兰(南半球,按 \(|\varphi|\) 处理) | +| Kazakhstan(Baikonur) | 45.6° | 哈萨克斯坦 | +| Alaska(Kodiak) | 57.4° | 高纬,惩罚最大 | + +对应曲线可在下图直观看到: + +![latitude_effects](latitude_effects.png) + +--- + +### 三方案“年份–能量”折线对比(100–300 年) + +下图是你要求的 **100–300 年**区间三条折线同图(不可完成的年份用空缺表示): + +![three_scenarios_comparison](three_scenarios_comparison.png) + +读图要点: +- **100–186 年**:只有混合方案可完成(电梯能力不足、火箭也不够快)。 +- **186–219 年**:电梯可完成;火箭仍无法完成;混合与电梯曲线重合(优先用电梯)。 +- **≥219 年**:三种方案都可完成,但纯火箭能耗最高。 + +为了写报告时更快引用,下面给出几组关键年份的能量读数(与脚本运行输出一致,单位 PJ): + +| 年份 | 混合方案 | 纯电梯 | 纯火箭 | +|---:|---:|---:|---:| +| 110 | 29852 | N/A | N/A | +| 150 | 22257 | N/A | N/A | +| 186 | 15720 | 15720 | N/A | +| 220 | 15720 | 15720 | 50605 | +| 300 | 15720 | 15720 | 50187 | + +--- + +### 可调参数(最常改的地方) + +#### 火箭相关(`rocket_launch_scenario.py` / `combined_scenario.py`) + +- `TOTAL_PAYLOAD`:总物资(吨) +- `PAYLOAD_PER_LAUNCH`:单次发射载荷(吨,默认 125) +- `ISP`、`ALPHA`、`NUM_STAGES`:推进系统与结构参数 +- `SPECIFIC_FUEL_ENERGY`:燃料比能(J/kg) +- `DELTA_V_BASE`:赤道基准 ΔV(m/s) +- `LAUNCH_SITES`:发射场纬度列表(可自行替换/增减) + +#### 太空电梯相关(`combined_scenario.py`) + +- `NUM_ELEVATORS`:电梯数量(默认 3) +- `ELEVATOR_CAPACITY_PER_YEAR`:**每个电梯年运力(吨/年)** + > 题面是 **179,000 t/year**;若你要用 **17,900 t/year**,直接改此值即可。 +- `ELEVATOR_SPECIFIC_ENERGY`:电梯总比能量(J/吨),默认 157.2 GJ/吨 + +--- + +### 重要假设与局限(写报告时建议明确) + +- **脉冲变轨/固定 ΔV**:将复杂轨道机动用固定 ΔV 预算概括。 +- **恒定比冲/结构系数**:忽略发动机工况变化、分级差异、载具设计差异。 +- **“能量”是工程近似口径**:火箭取推进剂化学能;电梯取提升+顶端段合并比能量。 +- **完美运行**:未考虑失败率、维护停机、窗口约束、天气/法规等运营因素。 +- **排布算法为贪心**:火箭发射按“低纬优先”分配以降低纬度惩罚;未做更复杂的成本/容量优化(可扩展为线性规划/整数规划)。 + +--- + +### 一句话结论(便于写 Summary) + +- **纯火箭**在“10 站 * 每日 1 发 * 125 吨/发”的约束下,最短约 **219 年**,且能耗约 **5×10⁴ PJ** 量级。 +- **纯电梯**能耗最低(约 **1.57×10⁴ PJ**),但最短约 **186 年**。 +- **混合方案**能把工期压到约 **101 年**,能耗介于两者之间,并随着工期增加逐步趋近纯电梯能耗。 + diff --git a/combined_scenario.py b/combined_scenario.py new file mode 100644 index 0000000..9b2952c --- /dev/null +++ b/combined_scenario.py @@ -0,0 +1,705 @@ +""" +Moon Colony Construction - Combined Scenario Analysis +(Space Elevator + Traditional Rockets) + +Problem: Deliver 100 million metric tons to Moon +- 3 Space Elevators: 179,000 metric tons/year each (priority) +- 10 Rocket launch sites: max 1 launch/day each, 125 tons/launch +- Optimize: Use elevators first (lower specific energy), then rockets + +Key: As timeline extends: +1. More payload handled by elevators (lower energy) +2. Remaining rocket launches shift to lower-latitude sites +""" + +import numpy as np +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +from matplotlib import rcParams +from dataclasses import dataclass +from typing import List, Tuple, Dict +import pandas as pd + +# 设置字体 +rcParams['font.sans-serif'] = ['Arial Unicode MS', 'DejaVu Sans', 'SimHei'] +rcParams['axes.unicode_minus'] = False + +# ============== 物理常数 ============== +G0 = 9.81 # m/s² +OMEGA_EARTH = 7.27e-5 # rad/s +R_EARTH = 6.371e6 # m + +# ============== 任务参数 ============== +TOTAL_PAYLOAD = 100e6 # 100 million metric tons + +# ============== 太空电梯参数 ============== +NUM_ELEVATORS = 3 +ELEVATOR_CAPACITY_PER_YEAR = 179000 # metric tons per elevator per year +TOTAL_ELEVATOR_CAPACITY = NUM_ELEVATORS * ELEVATOR_CAPACITY_PER_YEAR # 537,000 tons/year + +# 太空电梯能量参数 (从之前分析) +# 电梯提升能量: ~88.7 MJ/kg +# 顶端推进能量: ~68.5 MJ/kg +# 总计: ~157.2 MJ/kg = 157.2 TJ/1000 tons +ELEVATOR_SPECIFIC_ENERGY = 157.2e9 # J per metric ton (157.2 MJ/kg * 1000 kg) + +# ============== 火箭参数 ============== +PAYLOAD_PER_LAUNCH = 125 # metric tons per launch +ISP = 450 # 比冲 (秒) +SPECIFIC_FUEL_ENERGY = 15.5e6 # J/kg +ALPHA = 0.10 # 结构系数 +NUM_STAGES = 3 +DELTA_V_BASE = 13300 # m/s (赤道发射到月球) + +# 火箭比能量 (从之前分析: ~492.7 MJ/kg = 492.7 TJ/1000 tons for equator) +# 实际会因纬度变化 + + +# ============== 发射场定义 ============== +@dataclass +class LaunchSite: + name: str + short_name: str + latitude: float + max_launches_per_day: int = 1 + + @property + def abs_latitude(self) -> float: + return abs(self.latitude) + + @property + def rotation_velocity(self) -> float: + return OMEGA_EARTH * R_EARTH * np.cos(np.radians(self.abs_latitude)) + + @property + def delta_v_loss(self) -> float: + v_equator = OMEGA_EARTH * R_EARTH + return v_equator - self.rotation_velocity + + @property + def total_delta_v(self) -> float: + return DELTA_V_BASE + self.delta_v_loss + + +# 10个发射场 (按纬度排序) +LAUNCH_SITES = sorted([ + LaunchSite("Kourou (French Guiana)", "Kourou", 5.2), + LaunchSite("Satish Dhawan (India)", "SDSC", 13.7), + LaunchSite("Boca Chica (Texas)", "Texas", 26.0), + LaunchSite("Cape Canaveral (Florida)", "Florida", 28.5), + LaunchSite("Vandenberg (California)", "California", 34.7), + LaunchSite("Wallops (Virginia)", "Virginia", 37.8), + LaunchSite("Taiyuan (China)", "Taiyuan", 38.8), + LaunchSite("Mahia (New Zealand)", "Mahia", 39.3), + LaunchSite("Baikonur (Kazakhstan)", "Baikonur", 45.6), + LaunchSite("Kodiak (Alaska)", "Alaska", 57.4), +], key=lambda x: x.abs_latitude) + + +# ============== 核心计算函数 ============== + +def fuel_ratio_multistage(delta_v: float) -> float: + """多级火箭燃料/载荷比""" + ve = ISP * G0 + delta_v_per_stage = delta_v / NUM_STAGES + R_stage = np.exp(delta_v_per_stage / ve) + + denominator = 1 - ALPHA * (R_stage - 1) + if denominator <= 0: + return np.inf + + k_stage = (R_stage - 1) / denominator + + total_fuel_ratio = 0 + remaining_ratio = 1.0 + + for _ in range(NUM_STAGES): + fuel_this_stage = remaining_ratio * k_stage + total_fuel_ratio += fuel_this_stage + remaining_ratio *= (1 + k_stage * (1 + ALPHA)) + + return total_fuel_ratio + + +def rocket_energy_per_ton(site: LaunchSite) -> float: + """火箭发射每吨载荷的能量消耗 (J/ton)""" + k = fuel_ratio_multistage(site.total_delta_v) + fuel_per_ton = k * 1000 # kg fuel per metric ton payload + return fuel_per_ton * SPECIFIC_FUEL_ENERGY + + +def rocket_energy_per_launch(site: LaunchSite) -> float: + """单次火箭发射的能量消耗 (J)""" + return rocket_energy_per_ton(site) * PAYLOAD_PER_LAUNCH + + +# ============== 组合方案计算 ============== + +def calculate_combined_scenario(completion_years: float) -> Dict: + """ + 计算给定完成年限下的组合方案 + + 策略: + 1. 太空电梯满负荷运行 + 2. 剩余载荷由火箭发射,优先低纬度站点 + + 返回: + 方案详情字典 + """ + # 太空电梯运输量 + elevator_payload = min(TOTAL_ELEVATOR_CAPACITY * completion_years, TOTAL_PAYLOAD) + elevator_energy = elevator_payload * ELEVATOR_SPECIFIC_ENERGY + + # 剩余需要火箭运输的载荷 + remaining_payload = TOTAL_PAYLOAD - elevator_payload + + if remaining_payload <= 0: + # 完全由电梯完成 + return { + 'completion_years': completion_years, + 'elevator_payload': elevator_payload, + 'elevator_energy_PJ': elevator_energy / 1e15, + 'rocket_payload': 0, + 'rocket_energy_PJ': 0, + 'total_energy_PJ': elevator_energy / 1e15, + 'rocket_launches': 0, + 'sites_used': 0, + 'elevator_fraction': 1.0, + 'rocket_distribution': [], + } + + # 需要火箭发射 + rocket_launches_needed = int(np.ceil(remaining_payload / PAYLOAD_PER_LAUNCH)) + + # 按纬度优先分配火箭发射 + days_available = completion_years * 365 + max_launches_per_site = int(days_available) + + rocket_distribution = [] + remaining_launches = rocket_launches_needed + rocket_energy = 0 + sites_used = 0 + + for site in LAUNCH_SITES: + if remaining_launches <= 0: + rocket_distribution.append((site, 0)) + else: + allocated = min(remaining_launches, max_launches_per_site) + rocket_distribution.append((site, allocated)) + rocket_energy += rocket_energy_per_launch(site) * allocated + remaining_launches -= allocated + if allocated > 0: + sites_used += 1 + + # 检查是否能在规定时间内完成 + total_rocket_capacity = len(LAUNCH_SITES) * max_launches_per_site * PAYLOAD_PER_LAUNCH + if remaining_payload > total_rocket_capacity: + # 无法完成 + return None + + rocket_payload = rocket_launches_needed * PAYLOAD_PER_LAUNCH + total_energy = elevator_energy + rocket_energy + + return { + 'completion_years': completion_years, + 'elevator_payload': elevator_payload, + 'elevator_energy_PJ': elevator_energy / 1e15, + 'rocket_payload': rocket_payload, + 'rocket_energy_PJ': rocket_energy / 1e15, + 'total_energy_PJ': total_energy / 1e15, + 'rocket_launches': rocket_launches_needed, + 'sites_used': sites_used, + 'elevator_fraction': elevator_payload / TOTAL_PAYLOAD, + 'rocket_distribution': rocket_distribution, + } + + +def analyze_combined_timeline() -> pd.DataFrame: + """ + 分析不同完成年限下的组合方案 + """ + # 计算关键时间点 + # 1. 仅电梯完成所需时间 + elevator_only_years = TOTAL_PAYLOAD / TOTAL_ELEVATOR_CAPACITY + + # 2. 最短时间(电梯+全部火箭站点满负荷) + rocket_capacity_per_year = len(LAUNCH_SITES) * 365 * PAYLOAD_PER_LAUNCH + total_capacity_per_year = TOTAL_ELEVATOR_CAPACITY + rocket_capacity_per_year + min_years = TOTAL_PAYLOAD / total_capacity_per_year + + print(f"Elevator-only completion: {elevator_only_years:.1f} years") + print(f"Minimum completion (all systems): {min_years:.1f} years") + print(f"Elevator capacity: {TOTAL_ELEVATOR_CAPACITY:,} tons/year") + print(f"Rocket capacity: {rocket_capacity_per_year:,} tons/year") + + # 分析范围 + years_range = np.linspace(min_years, elevator_only_years * 1.1, 200) + + results = [] + for years in years_range: + scenario = calculate_combined_scenario(years) + if scenario is not None: + results.append(scenario) + + return pd.DataFrame(results) + + +# ============== 可视化 ============== + +def plot_combined_analysis( + save_path: str = '/Volumes/Files/code/mm/20260130_b/combined_scenario_analysis.png' +): + """ + 绘制组合方案分析图 + """ + df = analyze_combined_timeline() + + # 关键时间点 + elevator_only_years = TOTAL_PAYLOAD / TOTAL_ELEVATOR_CAPACITY + rocket_capacity_per_year = len(LAUNCH_SITES) * 365 * PAYLOAD_PER_LAUNCH + total_capacity_per_year = TOTAL_ELEVATOR_CAPACITY + rocket_capacity_per_year + min_years = TOTAL_PAYLOAD / total_capacity_per_year + + fig, axes = plt.subplots(2, 2, figsize=(16, 14)) + + # ========== 图1: 完成年份 vs 总能量消耗 ========== + ax1 = axes[0, 0] + + ax1.plot(df['completion_years'], df['total_energy_PJ'], 'b-', linewidth=2.5, + label='Total Energy') + ax1.plot(df['completion_years'], df['elevator_energy_PJ'], 'g--', linewidth=2, + label='Elevator Energy') + ax1.plot(df['completion_years'], df['rocket_energy_PJ'], 'r--', linewidth=2, + label='Rocket Energy') + + # 标记关键点 + ax1.axvline(x=min_years, color='orange', linestyle=':', linewidth=1.5, + label=f'Min time: {min_years:.0f} years') + ax1.axvline(x=elevator_only_years, color='purple', linestyle=':', linewidth=1.5, + label=f'Elevator-only: {elevator_only_years:.0f} years') + + ax1.set_xlabel('Completion Time (years)', fontsize=12) + ax1.set_ylabel('Energy Consumption (PJ)', fontsize=12) + ax1.set_title('Combined Scenario: Completion Time vs Energy\n' + '(Space Elevator + Rockets)', fontsize=14) + ax1.legend(loc='upper right') + ax1.grid(True, alpha=0.3) + ax1.set_xlim(min_years * 0.95, elevator_only_years * 1.05) + + # ========== 图2: 电梯 vs 火箭载荷比例 ========== + ax2 = axes[0, 1] + + ax2.fill_between(df['completion_years'], 0, df['elevator_fraction'] * 100, + alpha=0.7, color='green', label='Space Elevator') + ax2.fill_between(df['completion_years'], df['elevator_fraction'] * 100, 100, + alpha=0.7, color='red', label='Rockets') + + ax2.axvline(x=min_years, color='orange', linestyle=':', linewidth=1.5) + ax2.axvline(x=elevator_only_years, color='purple', linestyle=':', linewidth=1.5) + + ax2.set_xlabel('Completion Time (years)', fontsize=12) + ax2.set_ylabel('Payload Share (%)', fontsize=12) + ax2.set_title('Payload Distribution: Elevator vs Rockets', fontsize=14) + ax2.legend(loc='center right') + ax2.set_ylim(0, 100) + ax2.set_xlim(min_years * 0.95, elevator_only_years * 1.05) + ax2.grid(True, alpha=0.3) + + # ========== 图3: 使用的火箭发射场数量 ========== + ax3 = axes[1, 0] + + ax3.plot(df['completion_years'], df['sites_used'], 'r-', linewidth=2) + ax3.fill_between(df['completion_years'], 0, df['sites_used'], alpha=0.3, color='red') + + ax3.axvline(x=min_years, color='orange', linestyle=':', linewidth=1.5) + ax3.axvline(x=elevator_only_years, color='purple', linestyle=':', linewidth=1.5) + + # 标注发射场 + for i, site in enumerate(LAUNCH_SITES): + ax3.axhline(y=i+1, color='gray', linestyle=':', alpha=0.2) + ax3.text(df['completion_years'].max() * 0.99, i+1 + 0.15, + f'{site.short_name} ({site.abs_latitude:.0f}°)', + fontsize=8, ha='right', va='bottom') + + ax3.set_xlabel('Completion Time (years)', fontsize=12) + ax3.set_ylabel('Number of Rocket Sites Used', fontsize=12) + ax3.set_title('Rocket Launch Sites Required\n(Longer timeline → fewer high-lat sites)', fontsize=14) + ax3.set_ylim(0, 11) + ax3.set_xlim(min_years * 0.95, elevator_only_years * 1.05) + ax3.grid(True, alpha=0.3) + + # ========== 图4: 能量效率对比 ========== + ax4 = axes[1, 1] + + # 计算平均比能量 + avg_specific_energy = df['total_energy_PJ'] * 1e15 / TOTAL_PAYLOAD / 1e9 # GJ/ton + + ax4.plot(df['completion_years'], avg_specific_energy, 'b-', linewidth=2) + + # 参考线 + elevator_specific = ELEVATOR_SPECIFIC_ENERGY / 1e9 # GJ/ton + ax4.axhline(y=elevator_specific, color='green', linestyle='--', + label=f'Elevator only: {elevator_specific:.1f} GJ/ton') + + # 平均火箭比能量 (赤道) + rocket_specific_equator = rocket_energy_per_ton(LAUNCH_SITES[0]) / 1e9 + ax4.axhline(y=rocket_specific_equator, color='red', linestyle='--', + label=f'Rocket (equator): {rocket_specific_equator:.1f} GJ/ton') + + ax4.axvline(x=min_years, color='orange', linestyle=':', linewidth=1.5) + ax4.axvline(x=elevator_only_years, color='purple', linestyle=':', linewidth=1.5) + + ax4.set_xlabel('Completion Time (years)', fontsize=12) + ax4.set_ylabel('Average Specific Energy (GJ/ton)', fontsize=12) + ax4.set_title('Energy Efficiency vs Timeline\n(Longer time → more elevator → lower energy)', fontsize=14) + ax4.legend() + ax4.set_xlim(min_years * 0.95, elevator_only_years * 1.05) + ax4.grid(True, alpha=0.3) + + plt.suptitle(f'Combined Scenario: 100M tons to Moon\n' + f'3 Elevators ({TOTAL_ELEVATOR_CAPACITY:,} t/yr) + 10 Rocket Sites', + fontsize=15, y=1.02) + + plt.tight_layout() + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"\n组合方案分析图已保存至: {save_path}") + + return df + + +def plot_scenario_comparison( + save_path: str = '/Volumes/Files/code/mm/20260130_b/scenario_comparison.png' +): + """ + 对比三种方案:仅电梯、仅火箭、组合 + """ + # 关键时间点分析 + elevator_only_years = TOTAL_PAYLOAD / TOTAL_ELEVATOR_CAPACITY + rocket_capacity_per_year = len(LAUNCH_SITES) * 365 * PAYLOAD_PER_LAUNCH + rocket_only_min_years = TOTAL_PAYLOAD / rocket_capacity_per_year + combined_min_years = TOTAL_PAYLOAD / (TOTAL_ELEVATOR_CAPACITY + rocket_capacity_per_year) + + # 各方案能量 + elevator_only_energy = TOTAL_PAYLOAD * ELEVATOR_SPECIFIC_ENERGY / 1e15 # PJ + + # 火箭方案 (平均所有站点) + avg_rocket_energy_per_ton = sum(rocket_energy_per_ton(s) for s in LAUNCH_SITES) / len(LAUNCH_SITES) + rocket_only_energy = TOTAL_PAYLOAD * avg_rocket_energy_per_ton / 1e15 # PJ + + # 组合方案 (最短时间 - 加余量) + combined_scenario = calculate_combined_scenario(combined_min_years * 1.01) + combined_min_energy = combined_scenario['total_energy_PJ'] if combined_scenario else elevator_only_energy + + # 组合方案 (仅电梯时间) + combined_extended = calculate_combined_scenario(elevator_only_years * 0.95) + combined_extended_energy = combined_extended['total_energy_PJ'] if combined_extended else elevator_only_energy + + fig, axes = plt.subplots(1, 2, figsize=(14, 6)) + + # ========== 图1: 完成时间对比 ========== + ax1 = axes[0] + + scenarios = ['Elevator\nOnly', 'Rocket\nOnly', 'Combined\n(Min Time)', 'Combined\n(Extended)'] + times = [elevator_only_years, rocket_only_min_years, combined_min_years, elevator_only_years * 0.99] + colors = ['green', 'red', 'blue', 'purple'] + + bars = ax1.bar(scenarios, times, color=colors, alpha=0.7) + + ax1.set_ylabel('Completion Time (years)', fontsize=12) + ax1.set_title('Completion Time Comparison', fontsize=14) + ax1.grid(True, alpha=0.3, axis='y') + + for bar, t in zip(bars, times): + ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 2, + f'{t:.0f}y', ha='center', fontsize=11, fontweight='bold') + + # ========== 图2: 能量消耗对比 ========== + ax2 = axes[1] + + energies = [elevator_only_energy, rocket_only_energy, combined_min_energy, combined_extended_energy] + + bars = ax2.bar(scenarios, energies, color=colors, alpha=0.7) + + ax2.set_ylabel('Total Energy (PJ)', fontsize=12) + ax2.set_title('Energy Consumption Comparison', fontsize=14) + ax2.grid(True, alpha=0.3, axis='y') + + for bar, e in zip(bars, energies): + ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 500, + f'{e:.0f}', ha='center', fontsize=11, fontweight='bold') + + plt.tight_layout() + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"方案对比图已保存至: {save_path}") + + return { + 'elevator_only': {'years': elevator_only_years, 'energy_PJ': elevator_only_energy}, + 'rocket_only': {'years': rocket_only_min_years, 'energy_PJ': rocket_only_energy}, + 'combined_min': {'years': combined_min_years, 'energy_PJ': combined_min_energy}, + 'combined_extended': {'years': elevator_only_years * 0.99, 'energy_PJ': combined_extended_energy}, + } + + +def print_summary(df: pd.DataFrame): + """打印分析摘要""" + elevator_only_years = TOTAL_PAYLOAD / TOTAL_ELEVATOR_CAPACITY + rocket_capacity_per_year = len(LAUNCH_SITES) * 365 * PAYLOAD_PER_LAUNCH + combined_min_years = TOTAL_PAYLOAD / (TOTAL_ELEVATOR_CAPACITY + rocket_capacity_per_year) + + print("\n" + "=" * 90) + print("COMBINED SCENARIO (SPACE ELEVATOR + ROCKETS) ANALYSIS") + print("=" * 90) + + print(f"\nSystem Capacities:") + print(f" - 3 Space Elevators: {TOTAL_ELEVATOR_CAPACITY:,} tons/year") + print(f" - 10 Rocket Sites: {rocket_capacity_per_year:,} tons/year (@ 1 launch/day each)") + print(f" - Combined: {TOTAL_ELEVATOR_CAPACITY + rocket_capacity_per_year:,} tons/year") + + print(f"\nSpecific Energy:") + print(f" - Elevator: {ELEVATOR_SPECIFIC_ENERGY/1e9:.1f} GJ/ton ({ELEVATOR_SPECIFIC_ENERGY/1e9/rocket_energy_per_ton(LAUNCH_SITES[0])*1e9*100:.1f}% of rocket)") + print(f" - Rocket (Kourou): {rocket_energy_per_ton(LAUNCH_SITES[0])/1e9:.1f} GJ/ton") + print(f" - Rocket (Alaska): {rocket_energy_per_ton(LAUNCH_SITES[-1])/1e9:.1f} GJ/ton") + + print(f"\nKey Scenarios:") + + # 最短时间 (加一点余量确保可以完成) + min_scenario = calculate_combined_scenario(combined_min_years * 1.01) + print(f"\n 1. Minimum Time (~{combined_min_years:.0f} years):") + print(f" - Elevator payload: {min_scenario['elevator_payload']/1e6:.1f}M tons ({min_scenario['elevator_fraction']*100:.1f}%)") + print(f" - Rocket payload: {min_scenario['rocket_payload']/1e6:.1f}M tons") + print(f" - Rocket launches: {min_scenario['rocket_launches']:,}") + print(f" - Sites used: {min_scenario['sites_used']}") + print(f" - Total energy: {min_scenario['total_energy_PJ']:.0f} PJ") + + # 中等时间 + mid_years = (combined_min_years + elevator_only_years) / 2 + mid_scenario = calculate_combined_scenario(mid_years) + print(f"\n 2. Medium Timeline ({mid_years:.0f} years):") + print(f" - Elevator payload: {mid_scenario['elevator_payload']/1e6:.1f}M tons ({mid_scenario['elevator_fraction']*100:.1f}%)") + print(f" - Rocket payload: {mid_scenario['rocket_payload']/1e6:.1f}M tons") + print(f" - Rocket launches: {mid_scenario['rocket_launches']:,}") + print(f" - Sites used: {mid_scenario['sites_used']}") + print(f" - Total energy: {mid_scenario['total_energy_PJ']:.0f} PJ") + + # 接近仅电梯 + extended_years = elevator_only_years * 0.95 + extended_scenario = calculate_combined_scenario(extended_years) + print(f"\n 3. Extended Timeline ({extended_years:.0f} years):") + print(f" - Elevator payload: {extended_scenario['elevator_payload']/1e6:.1f}M tons ({extended_scenario['elevator_fraction']*100:.1f}%)") + print(f" - Rocket payload: {extended_scenario['rocket_payload']/1e6:.1f}M tons") + print(f" - Rocket launches: {extended_scenario['rocket_launches']:,}") + print(f" - Sites used: {extended_scenario['sites_used']}") + print(f" - Total energy: {extended_scenario['total_energy_PJ']:.0f} PJ") + + # 仅电梯 + print(f"\n 4. Elevator Only ({elevator_only_years:.0f} years):") + elevator_energy = TOTAL_PAYLOAD * ELEVATOR_SPECIFIC_ENERGY / 1e15 + print(f" - Elevator payload: 100M tons (100%)") + print(f" - Rocket launches: 0") + print(f" - Total energy: {elevator_energy:.0f} PJ") + + print("\n" + "=" * 90) + print("ENERGY SAVINGS ANALYSIS") + print("=" * 90) + + rocket_only_energy = TOTAL_PAYLOAD * sum(rocket_energy_per_ton(s) for s in LAUNCH_SITES) / len(LAUNCH_SITES) / 1e15 + + print(f"\n Baseline (Rocket Only): {rocket_only_energy:.0f} PJ") + print(f" Combined (Min Time): {min_scenario['total_energy_PJ']:.0f} PJ (saves {(1-min_scenario['total_energy_PJ']/rocket_only_energy)*100:.0f}%)") + print(f" Combined (Extended): {extended_scenario['total_energy_PJ']:.0f} PJ (saves {(1-extended_scenario['total_energy_PJ']/rocket_only_energy)*100:.0f}%)") + print(f" Elevator Only: {elevator_energy:.0f} PJ (saves {(1-elevator_energy/rocket_only_energy)*100:.0f}%)") + + print("=" * 90) + + +def plot_three_scenarios_comparison( + year_min: float = 100, + year_max: float = 300, + save_path: str = '/Volumes/Files/code/mm/20260130_b/three_scenarios_comparison.png' +): + """ + 绘制三种方案在指定年份范围内的能量消耗对比折线图 + + 方案: + 1. 纯火箭方案 (Rocket Only) + 2. 纯电梯方案 (Elevator Only) + 3. 混合方案 (Combined) + """ + years = np.linspace(year_min, year_max, 200) + + # 计算各方案的能量 + rocket_energy = [] + elevator_energy = [] + combined_energy = [] + + # 关键时间点 + elevator_only_min_years = TOTAL_PAYLOAD / TOTAL_ELEVATOR_CAPACITY # ~186 years + rocket_only_min_years = TOTAL_PAYLOAD / (len(LAUNCH_SITES) * 365 * PAYLOAD_PER_LAUNCH) # ~219 years + combined_min_years = TOTAL_PAYLOAD / (TOTAL_ELEVATOR_CAPACITY + len(LAUNCH_SITES) * 365 * PAYLOAD_PER_LAUNCH) # ~101 years + + for y in years: + # ===== 纯火箭方案 ===== + if y >= rocket_only_min_years: + # 可以完成,优先使用低纬度站点 + total_launches = int(TOTAL_PAYLOAD / PAYLOAD_PER_LAUNCH) + days_available = y * 365 + max_per_site = int(days_available) + + energy = 0 + remaining = total_launches + for site in LAUNCH_SITES: + if remaining <= 0: + break + allocated = min(remaining, max_per_site) + energy += rocket_energy_per_launch(site) * allocated + remaining -= allocated + + rocket_energy.append(energy / 1e15) + else: + # 无法完成 + rocket_energy.append(np.nan) + + # ===== 纯电梯方案 ===== + if y >= elevator_only_min_years: + # 可以完成 + energy = TOTAL_PAYLOAD * ELEVATOR_SPECIFIC_ENERGY + elevator_energy.append(energy / 1e15) + else: + # 无法完成 + elevator_energy.append(np.nan) + + # ===== 混合方案 ===== + if y >= combined_min_years: + scenario = calculate_combined_scenario(y) + if scenario: + combined_energy.append(scenario['total_energy_PJ']) + else: + combined_energy.append(np.nan) + else: + combined_energy.append(np.nan) + + # 创建图表 + fig, ax = plt.subplots(figsize=(14, 8)) + + # 绘制三条折线 + ax.plot(years, rocket_energy, 'r-', linewidth=2.5, label='Rocket Only', marker='', markersize=4) + ax.plot(years, elevator_energy, 'g-', linewidth=2.5, label='Elevator Only', marker='', markersize=4) + ax.plot(years, combined_energy, 'b-', linewidth=2.5, label='Combined (Elevator + Rocket)', marker='', markersize=4) + + # 标记关键时间点 + ax.axvline(x=combined_min_years, color='blue', linestyle=':', alpha=0.7, linewidth=1.5) + ax.axvline(x=elevator_only_min_years, color='green', linestyle=':', alpha=0.7, linewidth=1.5) + ax.axvline(x=rocket_only_min_years, color='red', linestyle=':', alpha=0.7, linewidth=1.5) + + # 添加标注 + y_pos = ax.get_ylim()[1] * 0.95 + ax.annotate(f'Combined\nMin: {combined_min_years:.0f}y', + xy=(combined_min_years, y_pos), fontsize=9, ha='center', + bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.8)) + ax.annotate(f'Elevator\nMin: {elevator_only_min_years:.0f}y', + xy=(elevator_only_min_years, y_pos * 0.85), fontsize=9, ha='center', + bbox=dict(boxstyle='round', facecolor='lightgreen', alpha=0.8)) + ax.annotate(f'Rocket\nMin: {rocket_only_min_years:.0f}y', + xy=(rocket_only_min_years, y_pos * 0.75), fontsize=9, ha='center', + bbox=dict(boxstyle='round', facecolor='lightsalmon', alpha=0.8)) + + # 填充区域表示节能效果 + # Combined比Rocket节省的能量 + rocket_arr = np.array(rocket_energy) + combined_arr = np.array(combined_energy) + valid_mask = ~np.isnan(rocket_arr) & ~np.isnan(combined_arr) + ax.fill_between(years[valid_mask], combined_arr[valid_mask], rocket_arr[valid_mask], + alpha=0.2, color='purple', label='Energy saved by Combined vs Rocket') + + ax.set_xlabel('Completion Time (years)', fontsize=13) + ax.set_ylabel('Total Energy Consumption (PJ)', fontsize=13) + ax.set_title('Moon Colony Construction: Energy vs Completion Time\n' + '(100 Million Metric Tons Payload Comparison)', fontsize=15) + ax.legend(loc='upper right', fontsize=11) + ax.grid(True, alpha=0.3) + ax.set_xlim(year_min, year_max) + + # 添加数据表格 + # 选择几个关键年份的数据 + key_years = [110, 150, 186, 220, 250] + table_data = [] + for ky in key_years: + idx = np.argmin(np.abs(years - ky)) + table_data.append([ + f'{ky}', + f'{combined_energy[idx]:.0f}' if not np.isnan(combined_energy[idx]) else 'N/A', + f'{elevator_energy[idx]:.0f}' if not np.isnan(elevator_energy[idx]) else 'N/A', + f'{rocket_energy[idx]:.0f}' if not np.isnan(rocket_energy[idx]) else 'N/A', + ]) + + # 在图表下方添加表格 + table = ax.table( + cellText=table_data, + colLabels=['Year', 'Combined (PJ)', 'Elevator (PJ)', 'Rocket (PJ)'], + loc='bottom', + bbox=[0.15, -0.25, 0.7, 0.18], + cellLoc='center' + ) + table.auto_set_font_size(False) + table.set_fontsize(10) + table.scale(1, 1.5) + + # 调整布局给表格留空间 + plt.subplots_adjust(bottom=0.25) + + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"\n三方案对比图已保存至: {save_path}") + + # 打印关键数据 + print("\n" + "=" * 70) + print("THREE SCENARIOS ENERGY COMPARISON (100-300 years)") + print("=" * 70) + print(f"\n{'Year':<10} {'Combined':>15} {'Elevator':>15} {'Rocket':>15}") + print(f"{'':10} {'(PJ)':>15} {'(PJ)':>15} {'(PJ)':>15}") + print("-" * 55) + + for ky in [110, 130, 150, 186, 200, 220, 250, 300]: + idx = np.argmin(np.abs(years - ky)) + c = f'{combined_energy[idx]:.0f}' if not np.isnan(combined_energy[idx]) else 'N/A' + e = f'{elevator_energy[idx]:.0f}' if not np.isnan(elevator_energy[idx]) else 'N/A' + r = f'{rocket_energy[idx]:.0f}' if not np.isnan(rocket_energy[idx]) else 'N/A' + print(f"{ky:<10} {c:>15} {e:>15} {r:>15}") + + print("=" * 70) + + return fig + + +# ============== 主程序 ============== + +if __name__ == "__main__": + print("=" * 90) + print("MOON COLONY: COMBINED SCENARIO ANALYSIS") + print("(Space Elevator System + Traditional Rockets)") + print("=" * 90) + + # 主分析 + df = plot_combined_analysis() + + # 打印摘要 + print_summary(df) + + # 方案对比 + print("\n\n正在生成方案对比图...") + comparison = plot_scenario_comparison() + + print("\n" + "=" * 90) + print("FINAL COMPARISON") + print("=" * 90) + print(f"\n{'Scenario':<25} {'Years':>12} {'Energy (PJ)':>15} {'vs Rocket Only':>18}") + print("-" * 70) + for name, data in comparison.items(): + rocket_energy_val = comparison['rocket_only']['energy_PJ'] + savings = (1 - data['energy_PJ'] / rocket_energy_val) * 100 if rocket_energy_val > 0 else 0 + print(f"{name:<25} {data['years']:>12.0f} {data['energy_PJ']:>15.0f} {savings:>17.0f}%") + print("=" * 90) + + # 生成三方案对比折线图 (100-300年) + print("\n\n正在生成三方案对比折线图 (100-300年)...") + plot_three_scenarios_comparison(year_min=100, year_max=300) diff --git a/combined_scenario_analysis.png b/combined_scenario_analysis.png new file mode 100644 index 0000000..1d420fc Binary files /dev/null and b/combined_scenario_analysis.png differ diff --git a/earth_moon_transfer.py b/earth_moon_transfer.py deleted file mode 100644 index 5977f7a..0000000 --- a/earth_moon_transfer.py +++ /dev/null @@ -1,272 +0,0 @@ -""" -地月转移轨道:有效载荷与燃料消耗(能量)关系分析 - -基于齐奥尔科夫斯基火箭方程和霍曼转移轨道模型 -""" - -import numpy as np -import matplotlib -matplotlib.use('Agg') # 使用非交互式后端 -import matplotlib.pyplot as plt -from matplotlib import rcParams - -# 设置中文字体支持 -rcParams['font.sans-serif'] = ['Arial Unicode MS', 'SimHei', 'DejaVu Sans'] -rcParams['axes.unicode_minus'] = False - -# ============== 物理常数 ============== -G0 = 9.81 # 地表重力加速度 (m/s²) - -# 地月转移轨道典型ΔV预算 (m/s) -DELTA_V_TLI = 3100 # 地月转移注入 (Trans-Lunar Injection) -DELTA_V_LOI = 800 # 月球轨道捕获 (Lunar Orbit Insertion) -DELTA_V_TOTAL = DELTA_V_TLI + DELTA_V_LOI # 总ΔV - -# 不同发动机类型的比冲 (秒) -ENGINES = { - '固体火箭': 280, - '液氧煤油': 350, - '液氧液氢': 450, - '离子推进 (仅供参考)': 3000, -} - -# 结构系数 (结构质量/燃料质量) -ALPHA = 0 - - -def exhaust_velocity(isp): - """ - 计算排气速度 - - 参数: - isp: 比冲 (秒) - 返回: - 排气速度 (m/s) - """ - return isp * G0 - - -def mass_ratio(delta_v, isp): - """ - 计算质量比 R = m0/mf - - 基于齐奥尔科夫斯基方程: ΔV = ve * ln(m0/mf) - - 参数: - delta_v: 速度增量 (m/s) - isp: 比冲 (秒) - 返回: - 质量比 R - """ - ve = exhaust_velocity(isp) - return np.exp(delta_v / ve) - - -def fuel_mass(payload_mass, delta_v, isp, alpha=ALPHA): - """ - 计算所需燃料质量 - - 公式推导: - m0 = mp + ms + mf (初始质量 = 有效载荷 + 结构 + 燃料) - mf_final = mp + ms (最终质量) - ms = alpha * m_fuel (结构质量与燃料成比例) - - 由火箭方程: R = m0 / mf_final - 解得: m_fuel = (mp + ms) * (R - 1) - m_fuel = mp * (R - 1) / (1 - alpha * (R - 1)) - - 参数: - payload_mass: 有效载荷质量 (kg) - delta_v: 速度增量 (m/s) - isp: 比冲 (秒) - alpha: 结构系数 - 返回: - 燃料质量 (kg) - """ - R = mass_ratio(delta_v, isp) - denominator = 1 - alpha * (R - 1) - - if denominator <= 0: - # 物理上不可能: 结构质量太大 - return np.inf - - return payload_mass * (R - 1) / denominator - - -def total_energy(fuel_mass, isp): - """ - 计算燃料的动能(近似能量消耗) - - 能量 E = 0.5 * m_fuel * ve² - - 参数: - fuel_mass: 燃料质量 (kg) - isp: 比冲 (秒) - 返回: - 能量 (焦耳) - """ - ve = exhaust_velocity(isp) - return 0.5 * fuel_mass * ve ** 2 - - -def characteristic_energy(delta_v): - """ - 计算特征能量 C3 (用于轨道能量分析) - - C3 = V_infinity² (相对于地球的双曲线超速平方) - 对于地月转移,C3 ≈ -2 km²/s² (束缚轨道) - - 简化计算: C3 ≈ (ΔV)² - V_escape² - """ - # 近地轨道逃逸速度约 10.9 km/s,轨道速度约 7.8 km/s - # 从LEO出发需要的额外速度约 3.1 km/s - return (delta_v / 1000) ** 2 # km²/s² - - -def plot_payload_fuel_relationship(): - """ - 绘制有效载荷与燃料消耗的关系图 - """ - # 有效载荷范围: 100 kg 到 50000 kg - payloads = np.linspace(100, 50000, 500) - - fig, axes = plt.subplots(2, 2, figsize=(14, 12)) - - # ========== 图1: 不同发动机的燃料消耗 ========== - ax1 = axes[0, 0] - for engine_name, isp in ENGINES.items(): - fuels = [fuel_mass(p, DELTA_V_TOTAL, isp) for p in payloads] - fuels = np.array(fuels) - # 过滤掉无穷大值 - valid = fuels < 1e9 - ax1.plot(payloads[valid]/1000, fuels[valid]/1000, - label=f'{engine_name} (Isp={isp}s)', linewidth=2) - - ax1.set_xlabel('有效载荷质量 (吨)', fontsize=12) - ax1.set_ylabel('燃料质量 (吨)', fontsize=12) - ax1.set_title('地月转移:有效载荷 vs 燃料消耗\n(ΔV = 3.9 km/s)', fontsize=14) - ax1.legend(loc='upper left') - ax1.grid(True, alpha=0.3) - ax1.set_xlim(0, 50) - - # ========== 图2: 燃料/载荷比 ========== - ax2 = axes[0, 1] - for engine_name, isp in list(ENGINES.items())[:3]: # 排除离子推进 - ratios = [fuel_mass(p, DELTA_V_TOTAL, isp) / p for p in payloads] - ax2.axhline(y=ratios[0], label=f'{engine_name} (Isp={isp}s)', linewidth=2) - - ax2.set_xlabel('有效载荷质量 (吨)', fontsize=12) - ax2.set_ylabel('燃料/载荷 质量比', fontsize=12) - ax2.set_title('燃料效率比较\n(质量比与载荷无关,仅取决于Isp和ΔV)', fontsize=14) - ax2.legend() - ax2.grid(True, alpha=0.3) - ax2.set_ylim(0, 10) - - # ========== 图3: 能量消耗 ========== - ax3 = axes[1, 0] - for engine_name, isp in list(ENGINES.items())[:3]: - fuels = np.array([fuel_mass(p, DELTA_V_TOTAL, isp) for p in payloads]) - energies = total_energy(fuels, isp) / 1e12 # 转换为TJ - valid = energies < 1e6 - ax3.plot(payloads[valid]/1000, energies[valid], - label=f'{engine_name}', linewidth=2) - - ax3.set_xlabel('有效载荷质量 (吨)', fontsize=12) - ax3.set_ylabel('能量消耗 (TJ)', fontsize=12) - ax3.set_title('能量消耗 vs 有效载荷\nE = ½ × m_fuel × ve²', fontsize=14) - ax3.legend() - ax3.grid(True, alpha=0.3) - - # ========== 图4: ΔV 敏感性分析 ========== - ax4 = axes[1, 1] - delta_v_range = np.linspace(3000, 5000, 100) # 3-5 km/s - payload_fixed = 10000 # 10吨有效载荷 - isp_reference = 450 # 液氧液氢 - - fuel_vs_dv = [fuel_mass(payload_fixed, dv, isp_reference) for dv in delta_v_range] - ax4.plot(delta_v_range/1000, np.array(fuel_vs_dv)/1000, - 'b-', linewidth=2, label='燃料质量') - - # 标记关键点 - ax4.axvline(x=DELTA_V_TOTAL/1000, color='r', linestyle='--', - label=f'标准任务 ΔV={DELTA_V_TOTAL/1000} km/s') - - ax4.set_xlabel('ΔV (km/s)', fontsize=12) - ax4.set_ylabel('燃料质量 (吨)', fontsize=12) - ax4.set_title(f'ΔV敏感性分析\n(有效载荷=10吨, Isp=450s)', fontsize=14) - ax4.legend() - ax4.grid(True, alpha=0.3) - - plt.tight_layout() - plt.savefig('/Volumes/Files/code/mm/20260130_b/earth_moon_transfer_analysis.png', - dpi=150, bbox_inches='tight') - print("图表已生成!") - # plt.show() # 非交互式环境下注释掉 - - return fig - - -def print_summary(): - """ - 打印关键数据摘要 - """ - print("=" * 60) - print("地月转移轨道分析摘要") - print("=" * 60) - print(f"\n任务参数:") - print(f" - 总ΔV需求: {DELTA_V_TOTAL/1000:.1f} km/s") - print(f" - TLI (地月转移注入): {DELTA_V_TLI/1000:.1f} km/s") - print(f" - LOI (月球轨道捕获): {DELTA_V_LOI/1000:.1f} km/s") - print(f" - 结构系数 α: {ALPHA}") - - print(f"\n不同发动机的燃料效率 (每吨有效载荷所需燃料):") - print("-" * 50) - - payload_ref = 1000 # 1吨参考载荷 - - for engine_name, isp in ENGINES.items(): - R = mass_ratio(DELTA_V_TOTAL, isp) - fuel = fuel_mass(payload_ref, DELTA_V_TOTAL, isp) - ve = exhaust_velocity(isp) - - if fuel < 1e9: - print(f"\n {engine_name}:") - print(f" 比冲 Isp = {isp} s") - print(f" 排气速度 ve = {ve:.0f} m/s") - print(f" 质量比 R = {R:.2f}") - print(f" 燃料/载荷比 = {fuel/payload_ref:.2f}") - print(f" 1吨载荷需燃料: {fuel/1000:.2f} 吨") - else: - print(f"\n {engine_name}:") - print(f" 比冲 Isp = {isp} s (化学火箭无法实现)") - - print("\n" + "=" * 60) - print("关键数学关系:") - print("=" * 60) - print(""" - 齐奥尔科夫斯基方程: - ΔV = ve × ln(m₀/mf) - - 质量比: - R = exp(ΔV / ve) - - 燃料-载荷关系: - m_fuel / m_payload = (R - 1) / (1 - α(R - 1)) - - 能量消耗: - E = ½ × m_fuel × ve² - - 其中: - ve = Isp × g₀ (排气速度) - α = 结构系数 (约0.1) -""") - - -if __name__ == "__main__": - # 打印分析摘要 - print_summary() - - # 绘制关系图 - print("\n正在生成图表...") - fig = plot_payload_fuel_relationship() - print("图表已保存至: earth_moon_transfer_analysis.png") diff --git a/launch_capacity_analysis.png b/launch_capacity_analysis.png new file mode 100644 index 0000000..5b2973e Binary files /dev/null and b/launch_capacity_analysis.png differ diff --git a/launch_capacity_analysis.py b/launch_capacity_analysis.py new file mode 100644 index 0000000..48665b3 --- /dev/null +++ b/launch_capacity_analysis.py @@ -0,0 +1,785 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +火箭发射数上限预测分析 +对比 Logistic、Gompertz、Richards 三种增长模型 +分析不同历史数据窗口对预测的影响 +""" + +import pandas as pd +import numpy as np +from scipy.optimize import curve_fit +from scipy.stats import pearsonr +import matplotlib.pyplot as plt +import warnings +warnings.filterwarnings('ignore') + +plt.rcParams["font.sans-serif"] = ["Arial Unicode MS", "SimHei", "DejaVu Sans"] +plt.rcParams["axes.unicode_minus"] = False + + +# ============================================================ +# 1. 增长模型定义 +# ============================================================ + +def logistic(t, K, r, t0): + """ + Logistic 增长模型(对称S曲线) + N(t) = K / (1 + exp(-r*(t - t0))) + + 参数: + K : 环境容量/饱和上限 + r : 增长率 + t0 : 拐点时间(增长最快的时刻,此时 N = K/2) + """ + return K / (1 + np.exp(-r * (t - t0))) + + +def gompertz(t, K, b, c): + """ + Gompertz 增长模型(非对称S曲线,早期增长较快) + N(t) = K * exp(-b * exp(-c*t)) + + 参数: + K : 饱和上限 + b : 位移参数 + c : 增长率参数 + 拐点在 N = K/e ≈ 0.368K 处 + """ + return K * np.exp(-b * np.exp(-c * t)) + + +def richards(t, K, r, t0, v): + """ + Richards 曲线(广义 Logistic,可调节对称性) + N(t) = K / (1 + exp(-r*(t - t0)))^(1/v) + + 参数: + K : 饱和上限 + r : 增长率 + t0 : 拐点时间 + v : 形状参数(v=1 时退化为 Logistic) + """ + exp_term = np.exp(-r * (t - t0)) + # 避免数值溢出 + exp_term = np.clip(exp_term, 1e-10, 1e10) + return K / np.power(1 + exp_term, 1/v) + + +# ============================================================ +# 2. 数据加载与预处理 +# ============================================================ + +def load_data(filepath="rocket_launch_counts.csv"): + """加载并清洗火箭发射数据""" + df = pd.read_csv(filepath) + df = df.rename(columns={"YDate": "year", "Total": "launches"}) + df["year"] = pd.to_numeric(df["year"], errors="coerce") + df["launches"] = pd.to_numeric(df["launches"], errors="coerce") + df = df.dropna(subset=["year", "launches"]) + # 只保留完整年份数据(截至2025年,2026年数据不完整) + df = df[(df["year"] >= 1957) & (df["year"] <= 2025)] + df = df.astype({"year": int, "launches": int}) + df = df.sort_values("year").reset_index(drop=True) + return df + + +# ============================================================ +# 3. 模型拟合 +# ============================================================ + +def fit_model(model_func, t, y, model_name, p0, bounds): + """通用模型拟合函数""" + try: + popt, pcov = curve_fit(model_func, t, y, p0=p0, bounds=bounds, maxfev=50000) + perr = np.sqrt(np.diag(pcov)) + y_pred = model_func(t, *popt) + + # 计算拟合优度 + ss_res = np.sum((y - y_pred) ** 2) + ss_tot = np.sum((y - np.mean(y)) ** 2) + r_squared = 1 - (ss_res / ss_tot) + rmse = np.sqrt(np.mean((y - y_pred) ** 2)) + + return { + "success": True, + "params": popt, + "param_err": perr, + "r_squared": r_squared, + "rmse": rmse, + "y_pred": y_pred + } + except Exception as e: + return { + "success": False, + "error": str(e) + } + + +def fit_all_models(data, base_year=1957): + """对数据拟合所有三种模型""" + years = data["year"].values + launches = data["launches"].values + t = (years - base_year).astype(float) + + results = {} + + # Logistic 模型 - 提高上限边界以避免撞边界 + p0_log = [2000.0, 0.08, 70.0] + bounds_log = ([300, 0.01, 0], [500000, 1.0, 300]) + res = fit_model(logistic, t, launches, "Logistic", p0_log, bounds_log) + if res["success"]: + K, r, t0 = res["params"] + res["K"] = K + res["inflection_year"] = base_year + t0 + res["inflection_value"] = K / 2 + results["Logistic"] = res + + # Gompertz 模型 + p0_gom = [2000.0, 5.0, 0.03] + bounds_gom = ([300, 0.5, 0.005], [500000, 50.0, 0.5]) + res = fit_model(gompertz, t, launches, "Gompertz", p0_gom, bounds_gom) + if res["success"]: + K, b, c = res["params"] + res["K"] = K + # Gompertz 拐点:t = ln(b)/c, 值为 K/e + res["inflection_year"] = base_year + np.log(b) / c + res["inflection_value"] = K / np.e + results["Gompertz"] = res + + # Richards 模型 + p0_ric = [2000.0, 0.08, 70.0, 2.0] + bounds_ric = ([300, 0.01, 0, 0.1], [500000, 1.0, 300, 10.0]) + res = fit_model(richards, t, launches, "Richards", p0_ric, bounds_ric) + if res["success"]: + K, r, t0, v = res["params"] + res["K"] = K + res["inflection_year"] = base_year + t0 + res["inflection_value"] = K / (v + 1) ** (1/v) + res["v"] = v + results["Richards"] = res + + return results, years, launches, t, base_year + + +def predict_future(results, base_year, years_future): + """生成未来预测""" + t_future = years_future - base_year + predictions = {} + + for model_name, res in results.items(): + if not res["success"]: + continue + + params = res["params"] + if model_name == "Logistic": + pred = logistic(t_future, *params) + elif model_name == "Gompertz": + pred = gompertz(t_future, *params) + elif model_name == "Richards": + pred = richards(t_future, *params) + + predictions[model_name] = np.maximum(pred, 0) + + return predictions + + +# ============================================================ +# 4. 可视化 +# ============================================================ + +def plot_model_comparison(df, results_dict, save_path="launch_capacity_analysis.png"): + """绘制模型对比图""" + fig, axes = plt.subplots(2, 2, figsize=(16, 12)) + + # 颜色方案 + colors = { + "Logistic": "#E74C3C", + "Gompertz": "#3498DB", + "Richards": "#27AE60" + } + + # 子图1: 历史数据 + 三种模型拟合(全部数据) + ax1 = axes[0, 0] + results, years, launches, t, base_year = results_dict["全部数据"] + ax1.scatter(years, launches, color="gray", s=30, alpha=0.7, label="历史数据", zorder=3) + + years_curve = np.arange(years.min(), 2101) + t_curve = years_curve - base_year + + for model_name, res in results.items(): + if not res["success"]: + continue + params = res["params"] + if model_name == "Logistic": + pred = logistic(t_curve, *params) + elif model_name == "Gompertz": + pred = gompertz(t_curve, *params) + elif model_name == "Richards": + pred = richards(t_curve, *params) + + ax1.plot(years_curve, pred, color=colors[model_name], lw=2, + label=f"{model_name} (K={res['K']:.0f}, R²={res['r_squared']:.3f})") + + ax1.axvline(2025, color="gray", ls="--", lw=1, alpha=0.5) + ax1.set_xlabel("年份", fontsize=11) + ax1.set_ylabel("年发射次数", fontsize=11) + ax1.set_title("全部历史数据 (1957-2025) 模型拟合", fontsize=12) + ax1.legend(loc="upper left", fontsize=9) + ax1.grid(True, alpha=0.3) + ax1.set_xlim(1955, 2105) + ax1.set_ylim(0, None) + + # 子图2: 近15年数据拟合 + ax2 = axes[0, 1] + results, years, launches, t, base_year = results_dict["近15年"] + ax2.scatter(years, launches, color="gray", s=40, alpha=0.8, label="历史数据", zorder=3) + + years_curve = np.arange(2010, 2101) + t_curve = years_curve - base_year + + for model_name, res in results.items(): + if not res["success"]: + continue + params = res["params"] + if model_name == "Logistic": + pred = logistic(t_curve, *params) + elif model_name == "Gompertz": + pred = gompertz(t_curve, *params) + elif model_name == "Richards": + pred = richards(t_curve, *params) + + ax2.plot(years_curve, pred, color=colors[model_name], lw=2, + label=f"{model_name} (K={res['K']:.0f})") + + ax2.axvline(2025, color="gray", ls="--", lw=1, alpha=0.5) + ax2.set_xlabel("年份", fontsize=11) + ax2.set_ylabel("年发射次数", fontsize=11) + ax2.set_title("近15年数据 (2010-2025) 模型拟合", fontsize=12) + ax2.legend(loc="upper left", fontsize=9) + ax2.grid(True, alpha=0.3) + ax2.set_xlim(2008, 2105) + ax2.set_ylim(0, None) + + # 子图3: 只展示 Richards 模型的K值(因为其他模型撞边界) + ax3 = axes[1, 0] + windows = ["全部数据", "近25年", "近15年", "近10年"] + x = np.arange(len(windows)) + + k_values = [] + for window in windows: + if window in results_dict: + res = results_dict[window][0].get("Richards", {}) + k_values.append(res.get("K", 0) if res.get("success", False) else 0) + else: + k_values.append(0) + + bars = ax3.bar(x, k_values, 0.6, color=colors["Richards"], alpha=0.8, edgecolor='black') + + # 添加数值标签 + for bar, k in zip(bars, k_values): + if k > 0: + ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 500, + f'{k:.0f}', ha='center', va='bottom', fontsize=10, fontweight='bold') + + ax3.set_xlabel("数据窗口", fontsize=11) + ax3.set_ylabel("预测饱和上限 K (次/年)", fontsize=11) + ax3.set_title("Richards 模型: 不同数据窗口的饱和上限预测\n(Logistic/Gompertz 撞边界,不适用)", fontsize=11) + ax3.set_xticks(x) + ax3.set_xticklabels(windows) + ax3.grid(True, alpha=0.3, axis='y') + ax3.set_ylim(0, max(k_values) * 1.15 if k_values else 10000) + + # 子图4: Richards 模型拐点年份 + ax4 = axes[1, 1] + inflection_years = [] + inflection_values = [] + for window in windows: + if window in results_dict: + res = results_dict[window][0].get("Richards", {}) + if res.get("success", False): + inflection_years.append(res.get("inflection_year", 0)) + inflection_values.append(res.get("inflection_value", 0)) + else: + inflection_years.append(0) + inflection_values.append(0) + else: + inflection_years.append(0) + inflection_values.append(0) + + bars = ax4.bar(x, inflection_years, 0.6, color=colors["Richards"], alpha=0.8, edgecolor='black') + + # 添加标签(拐点年份和对应发射数) + for bar, year, val in zip(bars, inflection_years, inflection_values): + if year > 0: + ax4.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5, + f'{year:.0f}\n({val:.0f}次/年)', ha='center', va='bottom', fontsize=9) + + ax4.axhline(2025, color="red", ls="--", lw=2, alpha=0.8, label="当前年份 (2025)") + ax4.set_xlabel("数据窗口", fontsize=11) + ax4.set_ylabel("拐点年份", fontsize=11) + ax4.set_title("Richards 模型: 预测拐点年份\n(增长最快的时刻)", fontsize=11) + ax4.set_xticks(x) + ax4.set_xticklabels(windows) + ax4.legend(loc="lower right", fontsize=9) + ax4.grid(True, alpha=0.3, axis='y') + valid_years = [y for y in inflection_years if y > 0] + if valid_years: + ax4.set_ylim(2020, max(valid_years) + 15) + + plt.suptitle("火箭年发射数上限预测 - 多模型对比分析", fontsize=14, fontweight='bold', y=1.02) + plt.tight_layout() + plt.savefig(save_path, dpi=150, bbox_inches="tight") + plt.close() + print(f"图表已保存: {save_path}") + + +def plot_physical_vs_model(results_dict, physical_limits, save_path="launch_capacity_physical.png"): + """绘制物理约束与模型预测对比图""" + fig, axes = plt.subplots(1, 2, figsize=(14, 6)) + + # 左图:物理约束与模型预测K值对比 + ax1 = axes[0] + + # 物理约束 + phys_scenarios = list(physical_limits.keys()) + phys_values = list(physical_limits.values()) + x_phys = np.arange(len(phys_scenarios)) + bars1 = ax1.barh(x_phys, phys_values, 0.6, color='#3498DB', alpha=0.7, label='物理约束估计') + + # 添加数值标签 + for bar, val in zip(bars1, phys_values): + ax1.text(val + 50, bar.get_y() + bar.get_height()/2, + f'{val:,}', va='center', fontsize=10) + + # 添加模型预测(Richards) + richards_k = [] + for window in ["近25年", "近15年", "近10年"]: + if window in results_dict: + res = results_dict[window][0].get("Richards", {}) + if res.get("success", False): + richards_k.append(res["K"]) + + if richards_k: + avg_richards = np.mean(richards_k) + ax1.axvline(avg_richards, color='#E74C3C', ls='--', lw=2, + label=f'Richards模型平均 K={avg_richards:,.0f}') + + ax1.set_yticks(x_phys) + ax1.set_yticklabels(phys_scenarios) + ax1.set_xlabel("年发射上限 (次/年)", fontsize=11) + ax1.set_title("物理约束 vs 模型预测 (10个发射场)", fontsize=12) + ax1.legend(loc="lower right", fontsize=9) + ax1.grid(True, alpha=0.3, axis='x') + ax1.set_xlim(0, max(phys_values) * 1.3) + + # 右图:带物理约束的时间预测 + ax2 = axes[1] + + # 获取近15年Richards预测 + if "近15年" in results_dict: + results, years, launches, t, base_year = results_dict["近15年"] + res = results.get("Richards", {}) + + if res["success"]: + years_future = np.arange(2010, 2101) + t_future = years_future - base_year + pred = richards(t_future, *res["params"]) + + ax2.scatter(years, launches, color="gray", s=40, alpha=0.7, label="历史数据", zorder=3) + ax2.plot(years_future, pred, color='#27AE60', lw=2, label=f"Richards (K={res['K']:,.0f})") + + # 添加物理约束线 + phys_colors = ['#E74C3C', '#F39C12', '#3498DB', '#9B59B6', '#1ABC9C'] + for i, (scenario, limit) in enumerate(physical_limits.items()): + ax2.axhline(limit, color=phys_colors[i % len(phys_colors)], + ls='--', lw=1.5, alpha=0.7, label=f'{scenario}: {limit:,}') + + ax2.axvline(2025, color="gray", ls=":", lw=1, alpha=0.5) + ax2.set_xlabel("年份", fontsize=11) + ax2.set_ylabel("年发射次数", fontsize=11) + ax2.set_title("Richards预测 + 物理约束线", fontsize=12) + ax2.legend(loc="upper left", fontsize=8) + ax2.grid(True, alpha=0.3) + ax2.set_xlim(2008, 2105) + ax2.set_ylim(0, max(physical_limits.values()) * 1.2) + + plt.suptitle("物理约束分析 (10个发射场)", fontsize=14, fontweight='bold', y=1.02) + plt.tight_layout() + plt.savefig(save_path, dpi=150, bbox_inches="tight") + plt.close() + print(f"图表已保存: {save_path}") + + +def plot_future_predictions(results_dict, save_path="launch_capacity_predictions.png"): + """绘制未来预测图""" + fig, axes = plt.subplots(1, 2, figsize=(16, 6)) + + colors = { + "Logistic": "#E74C3C", + "Gompertz": "#3498DB", + "Richards": "#27AE60" + } + + # 左图:基于全部数据的预测 + ax1 = axes[0] + results, years, launches, t, base_year = results_dict["全部数据"] + years_future = np.arange(1957, 2151) + predictions = predict_future(results, base_year, years_future) + + ax1.scatter(years, launches, color="gray", s=20, alpha=0.6, label="历史数据", zorder=3) + for model_name, pred in predictions.items(): + ax1.plot(years_future, pred, color=colors[model_name], lw=2, label=model_name) + + ax1.axvline(2025, color="gray", ls="--", lw=1, alpha=0.5) + ax1.axvspan(2025, 2150, alpha=0.05, color="blue") + ax1.set_xlabel("年份", fontsize=11) + ax1.set_ylabel("年发射次数", fontsize=11) + ax1.set_title("基于全部历史数据的预测 (至2150年)", fontsize=12) + ax1.legend(loc="upper left", fontsize=10) + ax1.grid(True, alpha=0.3) + ax1.set_xlim(1955, 2155) + ax1.set_ylim(0, None) + + # 右图:基于近15年数据的预测 + ax2 = axes[1] + results, years, launches, t, base_year = results_dict["近15年"] + years_future = np.arange(2010, 2151) + predictions = predict_future(results, base_year, years_future) + + ax2.scatter(years, launches, color="gray", s=40, alpha=0.7, label="历史数据", zorder=3) + for model_name, pred in predictions.items(): + ax2.plot(years_future, pred, color=colors[model_name], lw=2, label=model_name) + + ax2.axvline(2025, color="gray", ls="--", lw=1, alpha=0.5) + ax2.axvspan(2025, 2150, alpha=0.05, color="blue") + ax2.set_xlabel("年份", fontsize=11) + ax2.set_ylabel("年发射次数", fontsize=11) + ax2.set_title("基于近15年数据的预测 (至2150年)", fontsize=12) + ax2.legend(loc="upper left", fontsize=10) + ax2.grid(True, alpha=0.3) + ax2.set_xlim(2008, 2155) + ax2.set_ylim(0, None) + + plt.suptitle("火箭年发射数长期预测", fontsize=14, fontweight='bold', y=1.02) + plt.tight_layout() + plt.savefig(save_path, dpi=150, bbox_inches="tight") + plt.close() + print(f"图表已保存: {save_path}") + + +# ============================================================ +# 5. 主程序 +# ============================================================ + +def bootstrap_K_estimate(data, model_func, p0, bounds, n_bootstrap=500, base_year=1957): + """Bootstrap 估计 K 值的不确定性""" + years = data["year"].values + launches = data["launches"].values + t = (years - base_year).astype(float) + n = len(t) + + K_samples = [] + for _ in range(n_bootstrap): + # 重采样 + idx = np.random.choice(n, n, replace=True) + t_boot = t[idx] + y_boot = launches[idx] + + try: + popt, _ = curve_fit(model_func, t_boot, y_boot, p0=p0, bounds=bounds, maxfev=10000) + K_samples.append(popt[0]) # K 是第一个参数 + except: + pass + + if len(K_samples) > 10: + return { + "mean": np.mean(K_samples), + "median": np.median(K_samples), + "std": np.std(K_samples), + "ci_5": np.percentile(K_samples, 5), + "ci_95": np.percentile(K_samples, 95), + "samples": K_samples + } + return None + + +def calculate_aic_bic(y_true, y_pred, n_params): + """计算 AIC 和 BIC""" + n = len(y_true) + ss_res = np.sum((y_true - y_pred) ** 2) + mse = ss_res / n + + # Log-likelihood (假设高斯误差) + log_likelihood = -n/2 * (np.log(2 * np.pi) + np.log(mse) + 1) + + aic = 2 * n_params - 2 * log_likelihood + bic = n_params * np.log(n) - 2 * log_likelihood + + return aic, bic + + +def main(): + print("=" * 70) + print("火箭年发射数上限预测分析") + print("模型: Logistic, Gompertz, Richards") + print("=" * 70) + + # 加载数据 + df = load_data() + print(f"\n数据范围: {df['year'].min()} - {df['year'].max()}") + print(f"数据点数: {len(df)}") + print(f"最近5年发射数:") + for _, row in df.tail(5).iterrows(): + print(f" {int(row['year'])}: {int(row['launches'])} 次") + + # 计算增长率 + recent = df[df["year"] >= 2020].copy() + if len(recent) > 1: + growth_rates = recent["launches"].pct_change().dropna() * 100 + print(f"\n2020年以来年均增长率: {growth_rates.mean():.1f}%") + + # 定义不同的数据窗口 + end_year = 2025 + windows = { + "全部数据": df[df["year"] <= end_year].copy(), + "近25年": df[(df["year"] >= 2000) & (df["year"] <= end_year)].copy(), + "近15年": df[(df["year"] >= 2010) & (df["year"] <= end_year)].copy(), + "近10年": df[(df["year"] >= 2015) & (df["year"] <= end_year)].copy(), + } + + # 存储所有结果 + all_results = {} + + # 对每个窗口进行拟合 + for window_name, data in windows.items(): + print(f"\n{'='*50}") + print(f"数据窗口: {window_name} ({data['year'].min()}-{data['year'].max()}, n={len(data)})") + print("=" * 50) + + results, years, launches, t, base_year = fit_all_models(data) + all_results[window_name] = (results, years, launches, t, base_year) + + for model_name, res in results.items(): + if res["success"]: + print(f"\n{model_name} 模型:") + print(f" 饱和上限 K = {res['K']:.0f} 次/年") + print(f" 拐点年份 = {res['inflection_year']:.1f}") + print(f" 拐点处发射数 = {res['inflection_value']:.0f} 次/年") + print(f" R² (拟合优度) = {res['r_squared']:.4f}") + print(f" RMSE = {res['rmse']:.2f}") + if model_name == "Richards": + print(f" 形状参数 v = {res.get('v', 'N/A'):.3f}") + else: + print(f"\n{model_name} 模型: 拟合失败 - {res.get('error', 'Unknown')}") + + # 生成未来预测表 + print("\n" + "=" * 70) + print("未来预测 (基于近15年数据)") + print("=" * 70) + + results, years, launches, t, base_year = all_results["近15年"] + future_years = np.array([2030, 2040, 2050, 2075, 2100]) + predictions = predict_future(results, base_year, future_years) + + print(f"\n{'年份':<10}", end="") + for model in ["Logistic", "Gompertz", "Richards"]: + print(f"{model:<15}", end="") + print() + print("-" * 55) + + for i, year in enumerate(future_years): + print(f"{year:<10}", end="") + for model in ["Logistic", "Gompertz", "Richards"]: + if model in predictions: + print(f"{predictions[model][i]:<15.0f}", end="") + else: + print(f"{'N/A':<15}", end="") + print() + + # 饱和上限汇总 + print("\n" + "=" * 70) + print("饱和上限 K 汇总 (次/年)") + print("=" * 70) + print(f"\n{'数据窗口':<20}", end="") + for model in ["Logistic", "Gompertz", "Richards"]: + print(f"{model:<15}", end="") + print() + print("-" * 65) + + for window_name in ["全部数据", "近25年", "近15年", "近10年"]: + results = all_results[window_name][0] + print(f"{window_name:<20}", end="") + for model in ["Logistic", "Gompertz", "Richards"]: + if model in results and results[model]["success"]: + print(f"{results[model]['K']:<15.0f}", end="") + else: + print(f"{'N/A':<15}", end="") + print() + + # 物理约束分析 + physical_limits = analyze_physical_constraints(n_sites=10) + + # 绘制图表 + print("\n" + "=" * 70) + print("生成图表...") + plot_model_comparison(df, all_results) + plot_future_predictions(all_results) + plot_physical_vs_model(all_results, physical_limits) + + # 保存详细结果到CSV + save_results_csv(all_results) + + print("\n" + "=" * 70) + print("分析完成!") + print("=" * 70) + + # Bootstrap 不确定性分析 + print("\n" + "=" * 70) + print("Bootstrap 不确定性分析 (基于近15年数据, 500次重采样)") + print("=" * 70) + + data_15 = windows["近15年"] + base_year = 1957 + + # Richards 模型 Bootstrap + p0_ric = [2000.0, 0.08, 70.0, 2.0] + bounds_ric = ([300, 0.01, 0, 0.1], [500000, 1.0, 300, 10.0]) + + boot_result = bootstrap_K_estimate(data_15, richards, p0_ric, bounds_ric, n_bootstrap=500, base_year=base_year) + if boot_result: + print(f"\nRichards 模型 K 值 Bootstrap 估计:") + print(f" 均值: {boot_result['mean']:.0f} 次/年") + print(f" 中位数: {boot_result['median']:.0f} 次/年") + print(f" 标准差: {boot_result['std']:.0f}") + print(f" 90% 置信区间: [{boot_result['ci_5']:.0f}, {boot_result['ci_95']:.0f}] 次/年") + + # AIC/BIC 模型选择 + print("\n" + "=" * 70) + print("模型选择 (AIC/BIC, 基于近15年数据)") + print("=" * 70) + + results_15, years_15, launches_15, t_15, _ = all_results["近15年"] + print(f"\n{'模型':<12} {'AIC':<12} {'BIC':<12} {'参数数':<8}") + print("-" * 44) + + for model_name, res in results_15.items(): + if res["success"]: + n_params = len(res["params"]) + aic, bic = calculate_aic_bic(launches_15, res["y_pred"], n_params) + print(f"{model_name:<12} {aic:<12.1f} {bic:<12.1f} {n_params:<8}") + + # 结论 + print("\n" + "=" * 70) + print("📊 分析结论与建议") + print("=" * 70) + + # 收集有效的K值(排除撞边界的) + valid_k_values = [] + for window_name, (results, _, _, _, _) in all_results.items(): + for model_name, res in results.items(): + if res["success"] and res["K"] < 100000: # 排除撞边界的 + valid_k_values.append((window_name, model_name, res["K"])) + + print("\n1. 模型适用性分析:") + print("-" * 50) + print(" • Logistic/Gompertz: 数据处于快速增长期,无法确定上限") + print(" (模型撞到边界,说明增长远未饱和)") + print(" • Richards 曲线: 额外的形状参数使其能给出更合理估计") + print(" (推荐用于当前阶段的上限预测)") + + print("\n2. 合理的上限估计 (Richards 模型):") + print("-" * 50) + for window_name, model_name, k in valid_k_values: + if model_name == "Richards" and window_name != "全部数据": + print(f" • {window_name}: K ≈ {k:.0f} 次/年") + + # Richards 结果的平均值 + richards_k = [k for w, m, k in valid_k_values if m == "Richards" and w != "全部数据"] + if richards_k: + print(f"\n 📌 综合估计: {int(np.mean(richards_k)):,} - {int(np.max(richards_k)):,} 次/年") + + print("\n3. 物理约束参考 (基于10个主要发射场):") + print("-" * 50) + print(" • 主要发射场数量: 10个") + print(" • 假设每发射场年最大容量:") + print(" - 保守估计: 50次/年 → 总上限 500 次/年") + print(" - 中等估计: 100次/年 → 总上限 1,000 次/年") + print(" - 乐观估计: 200次/年 → 总上限 2,000 次/年") + print(" - 极限估计 (SpaceX级别): 400次/年 → 总上限 4,000 次/年") + print(" • 2025年实际数据: 329次 (10发射场平均 33次/场)") + print(" • SpaceX Starship 单型号目标: 1,000+ 次/年") + + print("\n4. 预测不确定性:") + print("-" * 50) + print(" • 当前处于指数增长早期,上限估计高度不确定") + print(" • 建议每年用新数据更新预测") + print(" • 需关注技术突破(可重复使用、星舰等)对上限的影响") + + if boot_result: + print(f"\n Bootstrap 90%置信区间: {boot_result['ci_5']:.0f} - {boot_result['ci_95']:.0f} 次/年") + + +def analyze_physical_constraints(n_sites=10): + """ + 基于发射场物理约束的上限分析 + + 参数: + n_sites: 发射场数量 + """ + print("\n" + "=" * 70) + print(f"物理约束分析 (基于 {n_sites} 个发射场)") + print("=" * 70) + + # 不同情景下的单发射场年容量 + scenarios = { + "保守 (当前技术)": 50, + "中等 (技术进步)": 100, + "乐观 (快速周转)": 200, + "极限 (SpaceX级)": 400, + "理论极限 (每天1发)": 365, + } + + print(f"\n单发射场年发射容量假设:") + print("-" * 50) + + results = {} + for scenario, capacity_per_site in scenarios.items(): + total_capacity = n_sites * capacity_per_site + results[scenario] = total_capacity + print(f" {scenario:<20}: {capacity_per_site:>3} 次/场/年 → 总上限 {total_capacity:>5,} 次/年") + + # 带置信度的综合估计 + print(f"\n综合估计 ({n_sites} 发射场):") + print("-" * 50) + print(f" 50% 置信区间: {n_sites * 50:,} - {n_sites * 100:,} 次/年") + print(f" 90% 置信区间: {n_sites * 30:,} - {n_sites * 200:,} 次/年") + print(f" 理论最大值: {n_sites * 365:,} 次/年 (每天每场1发)") + + return results + + +def save_results_csv(all_results, filepath="launch_capacity_results.csv"): + """保存详细结果到CSV""" + rows = [] + for window_name, (results, _, _, _, _) in all_results.items(): + for model_name, res in results.items(): + if res["success"]: + row = { + "数据窗口": window_name, + "模型": model_name, + "饱和上限K": res["K"], + "拐点年份": res["inflection_year"], + "拐点发射数": res["inflection_value"], + "R²": res["r_squared"], + "RMSE": res["rmse"] + } + rows.append(row) + + df_results = pd.DataFrame(rows) + df_results.to_csv(filepath, index=False, encoding="utf-8-sig") + print(f"详细结果已保存: {filepath}") + + +if __name__ == "__main__": + main() diff --git a/launch_capacity_physical.png b/launch_capacity_physical.png new file mode 100644 index 0000000..5ba9462 Binary files /dev/null and b/launch_capacity_physical.png differ diff --git a/launch_capacity_predictions.png b/launch_capacity_predictions.png new file mode 100644 index 0000000..c9df37e Binary files /dev/null and b/launch_capacity_predictions.png differ diff --git a/launch_capacity_results.csv b/launch_capacity_results.csv new file mode 100644 index 0000000..c2b776a --- /dev/null +++ b/launch_capacity_results.csv @@ -0,0 +1,13 @@ +数据窗口,模型,饱和上限K,拐点年份,拐点发射数,R²,RMSE +全部数据,Logistic,405.92414403335295,2099.062819804582,202.96207201667647,0.07766486971853037,46.74814967377199 +全部数据,Gompertz,401.0329954671314,2051.6490738033563,147.53179426375786,0.07545958812791254,46.80400317046386 +全部数据,Richards,687.2209173212347,2256.999921701128,512.1739060128197,0.08220581306057739,46.632929826767096 +近25年,Logistic,499998.291933841,2107.433566292766,249999.1459669205,0.8047914079948382,29.605437976518306 +近25年,Gompertz,499999.9999896808,2216.1161350271577,183939.72058192495,0.779423273804876,31.47037146055304 +近25年,Richards,16788.895636148314,2070.4580199940424,10907.704452410562,0.8048416215235632,29.60163002650332 +近15年,Logistic,499999.9999933337,2082.166434872205,249999.99999666685,0.8979462268050302,23.639528906752357 +近15年,Gompertz,499999.9999999759,2153.207304429069,183939.72058571232,0.8798702599574121,25.647766611285153 +近15年,Richards,32126.622670684326,2061.1007442806736,20365.326256859138,0.8979860420054704,23.63491710078375 +近10年,Logistic,499999.9996950232,2069.5299620988108,249999.9998475116,0.9632290653308331,15.01250946110567 +近10年,Gompertz,499999.99999999994,2122.2979572477343,183939.72058572114,0.9542285701514692,16.74935931143396 +近10年,Richards,23814.980027911413,2051.157710362777,15373.812459292129,0.9632499410027958,15.00824738946148 diff --git a/launch_distribution_mid.png b/launch_distribution_mid.png new file mode 100644 index 0000000..e4db5c0 Binary files /dev/null and b/launch_distribution_mid.png differ diff --git a/launch_distribution_min.png b/launch_distribution_min.png new file mode 100644 index 0000000..7bd2818 Binary files /dev/null and b/launch_distribution_min.png differ diff --git a/launch_frequency_analysis.png b/launch_frequency_analysis.png new file mode 100644 index 0000000..07d231c Binary files /dev/null and b/launch_frequency_analysis.png differ diff --git a/launch_sites.csv b/launch_sites.csv new file mode 100644 index 0000000..c32d038 --- /dev/null +++ b/launch_sites.csv @@ -0,0 +1,24 @@ +Ӣ,䳡,䳡ڹ,2023,2024,2025 +Florida,̫ջأάǿվأ,,59,71,82 +California,DZ̫ջأDZɽɽأ,,30,46,66 +Florida,Ϻ,,13,23,27 +Virginia,˹,,3,1, +Texas,濨ǻ,,2,4, +,ȪǷ䳡,й,36,21,33 +,Ƿ䳡,й,15,19,18 +Taiyuan Satellite Launch Center (China),̫ԭǷ䳡,й,4,13, +,IJ췢䳡,й,9,8, +,ҵ췢䳡,й,0,1, +,㶫,й,2,2, +,ɽ,й,1,4, +,лĿ˺췢䳡,˹,7,5, +,췢䳡,˹,3,4, +Kazakhstan,ݿŬ췢䳡,˹̹,9,8, +Mahia Peninsula (New Zealand),ŷŬվ,,7,13, +Satish Dhawan Space Centre (India),ʲ,ӡ,7,5, +French Guiana,Ǻ,,3,3, +,ӵ,ձ,3,5, +,,ձ,0,2, +,Ƿ䳡,,3,1, +,ɳ³º,,2,2, +,ķϺ,,0,2, diff --git a/rocket launch counts.txt b/rocket launch counts.txt new file mode 100644 index 0000000..0ceaebe --- /dev/null +++ b/rocket launch counts.txt @@ -0,0 +1,78 @@ +# Bin YDate ValMin ValMax US SU RU CN F J AU D UK I I-ELDO IN BR KR I-ESA KP IR IL CYM NZ Total +# --- ---- ---- ---- (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19) (20) ----- + 0 1956 1956 1957 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 1 1957 1957 1958 1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 + 2 1958 1958 1959 23 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 28 + 3 1959 1959 1960 19 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 23 + 4 1960 1960 1961 29 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 38 + 5 1961 1961 1962 41 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 50 + 6 1962 1962 1963 59 22 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 81 + 7 1963 1963 1964 46 23 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 69 + 8 1964 1964 1965 64 36 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 100 + 9 1965 1965 1966 70 53 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 124 + 10 1966 1966 1967 77 51 0 0 1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 131 + 11 1967 1967 1968 61 74 0 0 2 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 139 + 12 1968 1968 1969 48 79 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 128 + 13 1969 1969 1970 41 82 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 125 + 14 1970 1970 1971 29 87 0 1 2 2 0 0 1 1 1 0 0 0 0 0 0 0 0 0 124 + 15 1971 1971 1972 33 91 0 1 2 2 0 0 1 2 1 0 0 0 0 0 0 0 0 0 133 + 16 1972 1972 1973 32 79 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 113 + 17 1973 1973 1974 25 89 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 116 + 18 1974 1974 1975 23 85 0 2 0 1 0 0 0 2 0 0 0 0 0 0 0 0 0 0 113 + 19 1975 1975 1976 30 93 0 3 3 2 0 0 0 1 0 0 0 0 0 0 0 0 0 0 132 + 20 1976 1976 1977 26 100 0 3 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 131 + 21 1977 1977 1978 26 102 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 130 + 22 1978 1978 1979 33 91 0 1 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 + 23 1979 1979 1980 16 89 0 1 0 2 0 0 0 0 0 1 0 0 1 0 0 0 0 0 110 + 24 1980 1980 1981 15 89 0 0 0 2 0 0 0 0 0 1 0 0 1 0 0 0 0 0 108 + 25 1981 1981 1982 19 100 0 1 0 3 0 0 0 0 0 1 0 0 2 0 0 0 0 0 126 + 26 1982 1982 1983 18 108 0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 129 + 27 1983 1983 1984 22 100 0 1 0 3 0 0 0 0 0 1 0 0 2 0 0 0 0 0 129 + 28 1984 1984 1985 22 97 0 3 3 3 0 0 0 0 0 0 0 0 1 0 0 0 0 0 129 + 29 1985 1985 1986 18 100 0 1 4 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 125 + 30 1986 1986 1987 9 94 0 2 3 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 110 + 31 1987 1987 1988 9 97 0 2 2 3 0 0 0 0 0 1 0 0 0 0 0 0 0 0 114 + 32 1988 1988 1989 11 94 0 4 7 2 0 0 0 1 0 1 0 0 0 0 0 1 0 0 121 + 33 1989 1989 1990 18 75 0 0 7 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 102 + 34 1990 1990 1991 27 79 0 5 6 3 0 0 0 0 0 0 0 0 0 0 0 1 0 0 121 + 35 1991 1991 1992 19 61 0 1 8 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 91 + 36 1992 1992 1993 29 0 55 4 7 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 97 + 37 1993 1993 1994 25 0 48 1 7 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 83 + 38 1994 1994 1995 27 0 49 5 8 2 0 0 0 0 0 2 0 0 0 0 0 0 0 0 93 + 39 1995 1995 1996 30 0 33 3 11 2 0 0 0 0 0 0 0 0 0 0 0 1 0 0 80 + 40 1996 1996 1997 33 0 27 4 10 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 77 + 41 1997 1997 1998 38 0 29 6 11 2 0 0 0 0 0 1 1 0 1 0 0 0 0 0 89 + 42 1998 1998 1999 36 0 25 6 11 2 0 0 0 0 0 0 0 0 0 1 0 1 0 0 82 + 43 1999 1999 2000 31 0 22 4 16 1 0 0 0 0 0 1 1 0 0 0 0 0 2 0 78 + 44 2000 2000 2001 29 0 32 5 16 1 0 0 0 0 0 0 0 0 0 0 0 0 2 0 85 + 45 2001 2001 2002 24 0 23 1 8 1 0 0 0 0 0 2 0 0 0 0 0 0 0 0 59 + 46 2002 2002 2003 18 0 25 5 12 3 0 0 0 0 0 1 0 0 0 0 0 1 0 0 65 + 47 2003 2003 2004 26 0 19 7 6 3 0 0 0 0 0 2 0 0 0 0 0 0 0 0 63 + 48 2004 2004 2005 19 0 23 8 3 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 55 + 49 2005 2005 2006 16 0 23 6 6 2 0 0 0 0 0 1 0 0 2 0 0 0 0 0 56 + 50 2006 2006 2007 23 0 23 6 7 6 0 0 0 0 0 1 0 0 0 0 0 0 0 0 66 + 51 2007 2007 2008 20 0 23 10 9 2 0 0 0 0 0 3 0 0 0 0 0 1 0 0 68 + 52 2008 2008 2009 20 0 26 11 7 1 0 0 0 0 0 3 0 0 0 0 1 0 0 0 69 + 53 2009 2009 2010 25 0 32 6 7 3 0 0 0 0 0 2 0 1 0 1 1 0 0 0 78 + 54 2010 2010 2011 15 0 30 15 7 2 0 0 0 0 0 3 0 1 0 0 0 1 0 0 74 + 55 2011 2011 2012 19 0 30 19 9 3 0 0 0 0 0 3 0 0 0 0 1 0 0 0 84 + 56 2012 2012 2013 16 0 23 19 10 2 0 0 0 0 0 2 0 0 1 2 3 0 0 0 78 + 57 2013 2013 2014 20 0 31 15 7 3 0 0 0 0 0 3 0 1 1 0 0 0 0 0 81 + 58 2014 2014 2015 24 0 32 16 10 4 0 0 0 0 0 4 0 0 1 0 0 1 0 0 92 + 59 2015 2015 2016 20 0 26 19 9 4 0 0 0 0 0 5 0 0 3 0 1 0 0 0 87 + 60 2016 2016 2017 22 0 17 22 11 4 0 0 0 0 0 7 0 0 0 1 0 1 0 0 85 + 61 2017 2017 2018 29 0 19 18 11 7 0 0 0 0 0 5 0 0 0 0 0 0 0 1 90 + 62 2018 2018 2019 31 0 17 39 11 6 0 0 0 0 0 7 0 0 0 0 0 0 0 3 114 + 63 2019 2019 2020 21 0 22 34 9 2 0 0 0 0 0 6 0 0 0 0 2 0 0 6 102 + 64 2020 2020 2021 37 0 12 39 10 4 0 0 0 0 0 2 0 0 0 0 2 1 0 7 114 + 65 2021 2021 2022 45 0 16 56 15 3 0 0 0 0 0 2 0 1 0 0 2 0 0 6 146 + 66 2022 2022 2023 78 0 21 64 5 1 0 0 0 0 0 5 0 1 1 0 1 0 0 9 186 + 67 2023 2023 2024 109 0 19 67 3 3 0 0 0 0 0 7 0 2 0 3 2 1 0 7 223 + 68 2024 2024 2025 145 0 17 68 2 7 0 0 0 0 0 5 0 0 1 1 4 0 0 13 263 + 69 2025 2025 2026 181 0 17 92 7 4 1 1 0 0 0 5 0 2 0 0 1 1 0 17 329 + 70 2026 2026 2027 12 0 0 7 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 2 22 + 71 Total Total Total 2352 2449 886 741 350 142 1 1 2 9 4 101 2 9 20 9 21 13 4 71 7187 +# Bin YDate ValMin ValMax US SU RU CN F J AU D UK I I-ELDO IN BR KR I-ESA KP IR IL CYM NZ Total +# --- ---- ---- ---- (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19) (20) ----- +# Data last updated: 2026 Jan 30 0451:24 +https://planet4589.org/space/stats/launches.html \ No newline at end of file diff --git a/rocket_comprehensive.png b/rocket_comprehensive.png new file mode 100644 index 0000000..0af18fc Binary files /dev/null and b/rocket_comprehensive.png differ diff --git a/rocket_launch_counts.csv b/rocket_launch_counts.csv new file mode 100644 index 0000000..d4a8721 --- /dev/null +++ b/rocket_launch_counts.csv @@ -0,0 +1,73 @@ +Bin,YDate,ValMin,ValMax,US,SU,RU,CN,F,J,AU,D,UK,I,I-ELDO,IN,BR,KR,I-ESA,KP,IR,IL,CYM,NZ,Total +0,1956,1956,1957,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +1,1957,1957,1958,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3 +2,1958,1958,1959,23,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28 +3,1959,1959,1960,19,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23 +4,1960,1960,1961,29,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,38 +5,1961,1961,1962,41,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50 +6,1962,1962,1963,59,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,81 +7,1963,1963,1964,46,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,69 +8,1964,1964,1965,64,36,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100 +9,1965,1965,1966,70,53,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124 +10,1966,1966,1967,77,51,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,131 +11,1967,1967,1968,61,74,0,0,2,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,139 +12,1968,1968,1969,48,79,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,128 +13,1969,1969,1970,41,82,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,125 +14,1970,1970,1971,29,87,0,1,2,2,0,0,1,1,1,0,0,0,0,0,0,0,0,0,124 +15,1971,1971,1972,33,91,0,1,2,2,0,0,1,2,1,0,0,0,0,0,0,0,0,0,133 +16,1972,1972,1973,32,79,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,113 +17,1973,1973,1974,25,89,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,116 +18,1974,1974,1975,23,85,0,2,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,113 +19,1975,1975,1976,30,93,0,3,3,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,132 +20,1976,1976,1977,26,100,0,3,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,131 +21,1977,1977,1978,26,102,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130 +22,1978,1978,1979,33,91,0,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128 +23,1979,1979,1980,16,89,0,1,0,2,0,0,0,0,0,1,0,0,1,0,0,0,0,0,110 +24,1980,1980,1981,15,89,0,0,0,2,0,0,0,0,0,1,0,0,1,0,0,0,0,0,108 +25,1981,1981,1982,19,100,0,1,0,3,0,0,0,0,0,1,0,0,2,0,0,0,0,0,126 +26,1982,1982,1983,18,108,0,1,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,129 +27,1983,1983,1984,22,100,0,1,0,3,0,0,0,0,0,1,0,0,2,0,0,0,0,0,129 +28,1984,1984,1985,22,97,0,3,3,3,0,0,0,0,0,0,0,0,1,0,0,0,0,0,129 +29,1985,1985,1986,18,100,0,1,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,125 +30,1986,1986,1987,9,94,0,2,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,110 +31,1987,1987,1988,9,97,0,2,2,3,0,0,0,0,0,1,0,0,0,0,0,0,0,0,114 +32,1988,1988,1989,11,94,0,4,7,2,0,0,0,1,0,1,0,0,0,0,0,1,0,0,121 +33,1989,1989,1990,18,75,0,0,7,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,102 +34,1990,1990,1991,27,79,0,5,6,3,0,0,0,0,0,0,0,0,0,0,0,1,0,0,121 +35,1991,1991,1992,19,61,0,1,8,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91 +36,1992,1992,1993,29,0,55,4,7,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,97 +37,1993,1993,1994,25,0,48,1,7,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,83 +38,1994,1994,1995,27,0,49,5,8,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,93 +39,1995,1995,1996,30,0,33,3,11,2,0,0,0,0,0,0,0,0,0,0,0,1,0,0,80 +40,1996,1996,1997,33,0,27,4,10,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,77 +41,1997,1997,1998,38,0,29,6,11,2,0,0,0,0,0,1,1,0,1,0,0,0,0,0,89 +42,1998,1998,1999,36,0,25,6,11,2,0,0,0,0,0,0,0,0,0,1,0,1,0,0,82 +43,1999,1999,2000,31,0,22,4,16,1,0,0,0,0,0,1,1,0,0,0,0,0,2,0,78 +44,2000,2000,2001,29,0,32,5,16,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,85 +45,2001,2001,2002,24,0,23,1,8,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,59 +46,2002,2002,2003,18,0,25,5,12,3,0,0,0,0,0,1,0,0,0,0,0,1,0,0,65 +47,2003,2003,2004,26,0,19,7,6,3,0,0,0,0,0,2,0,0,0,0,0,0,0,0,63 +48,2004,2004,2005,19,0,23,8,3,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,55 +49,2005,2005,2006,16,0,23,6,6,2,0,0,0,0,0,1,0,0,2,0,0,0,0,0,56 +50,2006,2006,2007,23,0,23,6,7,6,0,0,0,0,0,1,0,0,0,0,0,0,0,0,66 +51,2007,2007,2008,20,0,23,10,9,2,0,0,0,0,0,3,0,0,0,0,0,1,0,0,68 +52,2008,2008,2009,20,0,26,11,7,1,0,0,0,0,0,3,0,0,0,0,1,0,0,0,69 +53,2009,2009,2010,25,0,32,6,7,3,0,0,0,0,0,2,0,1,0,1,1,0,0,0,78 +54,2010,2010,2011,15,0,30,15,7,2,0,0,0,0,0,3,0,1,0,0,0,1,0,0,74 +55,2011,2011,2012,19,0,30,19,9,3,0,0,0,0,0,3,0,0,0,0,1,0,0,0,84 +56,2012,2012,2013,16,0,23,19,10,2,0,0,0,0,0,2,0,0,1,2,3,0,0,0,78 +57,2013,2013,2014,20,0,31,15,7,3,0,0,0,0,0,3,0,1,1,0,0,0,0,0,81 +58,2014,2014,2015,24,0,32,16,10,4,0,0,0,0,0,4,0,0,1,0,0,1,0,0,92 +59,2015,2015,2016,20,0,26,19,9,4,0,0,0,0,0,5,0,0,3,0,1,0,0,0,87 +60,2016,2016,2017,22,0,17,22,11,4,0,0,0,0,0,7,0,0,0,1,0,1,0,0,85 +61,2017,2017,2018,29,0,19,18,11,7,0,0,0,0,0,5,0,0,0,0,0,0,0,1,90 +62,2018,2018,2019,31,0,17,39,11,6,0,0,0,0,0,7,0,0,0,0,0,0,0,3,114 +63,2019,2019,2020,21,0,22,34,9,2,0,0,0,0,0,6,0,0,0,0,2,0,0,6,102 +64,2020,2020,2021,37,0,12,39,10,4,0,0,0,0,0,2,0,0,0,0,2,1,0,7,114 +65,2021,2021,2022,45,0,16,56,15,3,0,0,0,0,0,2,0,1,0,0,2,0,0,6,146 +66,2022,2022,2023,78,0,21,64,5,1,0,0,0,0,0,5,0,1,1,0,1,0,0,9,186 +67,2023,2023,2024,109,0,19,67,3,3,0,0,0,0,0,7,0,2,0,3,2,1,0,7,223 +68,2024,2024,2025,145,0,17,68,2,7,0,0,0,0,0,5,0,0,1,1,4,0,0,13,263 +69,2025,2025,2026,181,0,17,92,7,4,1,1,0,0,0,5,0,2,0,0,1,1,0,17,329 +70,2026,2026,2027,12,0,0,7,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,2,22 +71,Total,Total,Total,2352,2449,886,741,350,142,1,1,2,9,4,101,2,9,20,9,21,13,4,71,7187 diff --git a/rocket_launch_scenario.py b/rocket_launch_scenario.py new file mode 100644 index 0000000..4e9ff3f --- /dev/null +++ b/rocket_launch_scenario.py @@ -0,0 +1,678 @@ +""" +Moon Colony Construction - Rocket Launch Scenario Analysis + +Problem: Deliver 100 million metric tons to Moon using traditional rockets +- 10 launch sites, each max 1 launch per day +- Rocket payload: 100-150 metric tons (use 125 tons average) +- Optimize launch distribution to minimize energy consumption +- Plot completion year vs total energy consumption + +Key insight: As completion timeline extends, more launches can be allocated +to low-latitude sites, reducing the latitude-induced energy penalty. +""" + +import numpy as np +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +from matplotlib import rcParams +from dataclasses import dataclass +from typing import List, Tuple +import pandas as pd + +# 设置字体 +rcParams['font.sans-serif'] = ['Arial Unicode MS', 'DejaVu Sans', 'SimHei'] +rcParams['axes.unicode_minus'] = False + +# ============== 物理常数 ============== +G0 = 9.81 # m/s² +OMEGA_EARTH = 7.27e-5 # rad/s +R_EARTH = 6.371e6 # m + +# ============== 任务参数 ============== +TOTAL_PAYLOAD = 100e6 # 100 million metric tons = 100e9 kg 的物资,但单位是metric tons +PAYLOAD_PER_LAUNCH = 125 # metric tons per launch (average of 100-150) +TOTAL_LAUNCHES_NEEDED = TOTAL_PAYLOAD / PAYLOAD_PER_LAUNCH # 800,000 launches + +# 火箭参数 +ISP = 450 # 比冲 (秒) - 液氧液氢 +SPECIFIC_FUEL_ENERGY = 15.5e6 # J/kg 燃料比能量 +ALPHA = 0.10 # 结构系数 +NUM_STAGES = 3 # 3级火箭 + +# 地面发射基准 ΔV (赤道发射到月球轨道) +DELTA_V_BASE = 13300 # m/s (LEO + TLI + LOI) + + +# ============== 发射场定义 ============== +@dataclass +class LaunchSite: + name: str + short_name: str + latitude: float # degrees + max_launches_per_day: int = 1 + + @property + def abs_latitude(self) -> float: + return abs(self.latitude) + + @property + def rotation_velocity(self) -> float: + """地球自转速度 (m/s)""" + return OMEGA_EARTH * R_EARTH * np.cos(np.radians(self.abs_latitude)) + + @property + def delta_v_loss(self) -> float: + """相对赤道的ΔV损失 (m/s)""" + v_equator = OMEGA_EARTH * R_EARTH # ~465 m/s + return v_equator - self.rotation_velocity + + @property + def total_delta_v(self) -> float: + """总ΔV需求 (m/s)""" + return DELTA_V_BASE + self.delta_v_loss + + +# 10个发射场 (按纬度排序) +LAUNCH_SITES = [ + LaunchSite("Kourou (French Guiana)", "Kourou", 5.2), + LaunchSite("Satish Dhawan (India)", "SDSC", 13.7), + LaunchSite("Boca Chica (Texas)", "Texas", 26.0), + LaunchSite("Cape Canaveral (Florida)", "Florida", 28.5), + LaunchSite("Vandenberg (California)", "California", 34.7), + LaunchSite("Wallops (Virginia)", "Virginia", 37.8), + LaunchSite("Taiyuan (China)", "Taiyuan", 38.8), + LaunchSite("Mahia (New Zealand)", "Mahia", 39.3), + LaunchSite("Baikonur (Kazakhstan)", "Baikonur", 45.6), + LaunchSite("Kodiak (Alaska)", "Alaska", 57.4), +] + +# 按纬度排序 +LAUNCH_SITES = sorted(LAUNCH_SITES, key=lambda x: x.abs_latitude) + + +# ============== 核心计算函数 ============== + +def fuel_ratio_multistage(delta_v: float, isp: float = ISP, alpha: float = ALPHA, + num_stages: int = NUM_STAGES) -> float: + """ + 多级火箭燃料/载荷比 + """ + ve = isp * G0 + delta_v_per_stage = delta_v / num_stages + R_stage = np.exp(delta_v_per_stage / ve) + + denominator = 1 - alpha * (R_stage - 1) + if denominator <= 0: + return np.inf + + k_stage = (R_stage - 1) / denominator + + # 多级计算 + total_fuel_ratio = 0 + remaining_ratio = 1.0 + + for _ in range(num_stages): + fuel_this_stage = remaining_ratio * k_stage + total_fuel_ratio += fuel_this_stage + remaining_ratio *= (1 + k_stage * (1 + alpha)) + + return total_fuel_ratio + + +def energy_per_launch(site: LaunchSite, payload_tons: float = PAYLOAD_PER_LAUNCH) -> float: + """ + 计算单次发射的能量消耗 (Joules) + + 参数: + site: 发射场 + payload_tons: 载荷 (metric tons) + 返回: + 能量消耗 (J) + """ + delta_v = site.total_delta_v + k = fuel_ratio_multistage(delta_v) + + payload_kg = payload_tons * 1000 # convert to kg + fuel_kg = k * payload_kg + + energy = fuel_kg * SPECIFIC_FUEL_ENERGY + return energy + + +def fuel_per_launch(site: LaunchSite, payload_tons: float = PAYLOAD_PER_LAUNCH) -> float: + """ + 计算单次发射的燃料消耗 (metric tons) + """ + delta_v = site.total_delta_v + k = fuel_ratio_multistage(delta_v) + return k * payload_tons + + +# ============== 发射排布优化 ============== + +def calculate_launch_distribution( + total_launches: int, + completion_years: float, + sites: List[LaunchSite] = LAUNCH_SITES +) -> List[Tuple[LaunchSite, int]]: + """ + 计算给定完成年限下的发射分配 + + 策略:优先使用低纬度发射场,直到达到其最大容量 + + 参数: + total_launches: 总发射次数 + completion_years: 完成年限 + sites: 发射场列表 (已按纬度排序) + + 返回: + [(发射场, 分配的发射次数), ...] + """ + days_available = completion_years * 365 + max_launches_per_site = days_available # 每站每天最多1发 + + distribution = [] + remaining_launches = total_launches + + # 按纬度从低到高分配 + for site in sites: + if remaining_launches <= 0: + distribution.append((site, 0)) + else: + allocated = min(remaining_launches, max_launches_per_site) + distribution.append((site, int(allocated))) + remaining_launches -= allocated + + return distribution + + +def calculate_total_energy(distribution: List[Tuple[LaunchSite, int]]) -> dict: + """ + 计算发射分配的总能量消耗 + + 返回: + 包含各项统计的字典 + """ + total_energy = 0 + total_fuel = 0 + total_launches = 0 + site_details = [] + + for site, num_launches in distribution: + if num_launches > 0: + energy = energy_per_launch(site) * num_launches + fuel = fuel_per_launch(site) * num_launches + total_energy += energy + total_fuel += fuel + total_launches += num_launches + + site_details.append({ + 'site': site.name, + 'short_name': site.short_name, + 'latitude': site.abs_latitude, + 'launches': num_launches, + 'energy_PJ': energy / 1e15, # PetaJoules + 'fuel_Mt': fuel / 1e6, # Million metric tons + }) + + return { + 'total_energy_PJ': total_energy / 1e15, + 'total_fuel_Mt': total_fuel / 1e6, + 'total_launches': total_launches, + 'sites': site_details + } + + +def analyze_completion_timeline( + years_range: np.ndarray, + total_payload: float = TOTAL_PAYLOAD, + payload_per_launch: float = PAYLOAD_PER_LAUNCH +) -> pd.DataFrame: + """ + 分析不同完成年限下的能量消耗 + + 返回: + DataFrame with analysis results + """ + total_launches = int(total_payload / payload_per_launch) + + results = [] + for years in years_range: + distribution = calculate_launch_distribution(total_launches, years) + stats = calculate_total_energy(distribution) + + # 计算使用了多少个发射场 + sites_used = sum(1 for _, n in distribution if n > 0) + + # 计算低纬度发射场的比例 + low_lat_launches = sum(n for site, n in distribution if site.abs_latitude < 30) + low_lat_ratio = low_lat_launches / total_launches if total_launches > 0 else 0 + + results.append({ + 'years': years, + 'total_launches': total_launches, + 'sites_used': sites_used, + 'total_energy_PJ': stats['total_energy_PJ'], + 'total_fuel_Mt': stats['total_fuel_Mt'], + 'low_lat_ratio': low_lat_ratio, + 'energy_per_launch_TJ': stats['total_energy_PJ'] * 1000 / total_launches, + }) + + return pd.DataFrame(results) + + +# ============== 可视化 ============== + +def plot_timeline_analysis( + save_path: str = '/Volumes/Files/code/mm/20260130_b/rocket_launch_timeline.png' +): + """ + 绘制完成年份-能量消耗关系图 + """ + # 计算最短完成时间 + total_launches = int(TOTAL_PAYLOAD / PAYLOAD_PER_LAUNCH) + num_sites = len(LAUNCH_SITES) + min_years = total_launches / (num_sites * 365) + + print(f"Total payload: {TOTAL_PAYLOAD/1e6:.0f} million metric tons") + print(f"Payload per launch: {PAYLOAD_PER_LAUNCH} metric tons") + print(f"Total launches needed: {total_launches:,}") + print(f"Number of launch sites: {num_sites}") + print(f"Minimum completion time: {min_years:.1f} years") + + # 分析范围:从最短时间到5倍最短时间 + years_range = np.linspace(min_years, min_years * 5, 100) + + # 运行分析 + df = analyze_completion_timeline(years_range) + + # 创建图表 + fig, axes = plt.subplots(2, 2, figsize=(16, 14)) + + # ========== 图1: 完成年份 vs 总能量消耗 ========== + ax1 = axes[0, 0] + ax1.plot(df['years'], df['total_energy_PJ'], 'b-', linewidth=2) + ax1.fill_between(df['years'], df['total_energy_PJ'], alpha=0.3) + + # 标记关键点 + min_idx = df['total_energy_PJ'].idxmin() + ax1.axvline(x=df.loc[min_idx, 'years'], color='r', linestyle='--', + label=f'Optimal: {df.loc[min_idx, "years"]:.1f} years') + ax1.axhline(y=df.loc[min_idx, 'total_energy_PJ'], color='r', linestyle=':', alpha=0.5) + + # 标记最短时间点 + ax1.axvline(x=min_years, color='g', linestyle='--', + label=f'Minimum time: {min_years:.1f} years') + + ax1.set_xlabel('Completion Time (years)', fontsize=12) + ax1.set_ylabel('Total Energy Consumption (PJ)', fontsize=12) + ax1.set_title('Completion Timeline vs Total Energy\n(100 Million Metric Tons to Moon)', fontsize=14) + ax1.legend() + ax1.grid(True, alpha=0.3) + + # ========== 图2: 使用的发射场数量 ========== + ax2 = axes[0, 1] + ax2.plot(df['years'], df['sites_used'], 'g-', linewidth=2, marker='o', + markevery=10, markersize=6) + + ax2.set_xlabel('Completion Time (years)', fontsize=12) + ax2.set_ylabel('Number of Launch Sites Used', fontsize=12) + ax2.set_title('Launch Sites Required vs Timeline', fontsize=14) + ax2.set_ylim(0, 11) + ax2.grid(True, alpha=0.3) + + # 添加发射场名称标注 + for i, site in enumerate(LAUNCH_SITES): + ax2.axhline(y=i+1, color='gray', linestyle=':', alpha=0.3) + ax2.text(df['years'].max() * 0.98, i+1 + 0.1, site.short_name, + fontsize=8, ha='right', va='bottom') + + # ========== 图3: 低纬度发射比例 ========== + ax3 = axes[1, 0] + ax3.plot(df['years'], df['low_lat_ratio'] * 100, 'm-', linewidth=2) + ax3.fill_between(df['years'], df['low_lat_ratio'] * 100, alpha=0.3, color='magenta') + + ax3.set_xlabel('Completion Time (years)', fontsize=12) + ax3.set_ylabel('Low Latitude (<30°) Launch Ratio (%)', fontsize=12) + ax3.set_title('Proportion of Launches from Low-Latitude Sites', fontsize=14) + ax3.set_ylim(0, 105) + ax3.grid(True, alpha=0.3) + + # ========== 图4: 平均每发能量 ========== + ax4 = axes[1, 1] + ax4.plot(df['years'], df['energy_per_launch_TJ'], 'r-', linewidth=2) + + # 计算赤道和最高纬度的能量参考线 + energy_equator = energy_per_launch(LAUNCH_SITES[0]) / 1e12 # TJ + energy_max_lat = energy_per_launch(LAUNCH_SITES[-1]) / 1e12 # TJ + + ax4.axhline(y=energy_equator, color='g', linestyle='--', + label=f'Equator baseline: {energy_equator:.1f} TJ') + ax4.axhline(y=energy_max_lat, color='orange', linestyle='--', + label=f'Alaska (57°): {energy_max_lat:.1f} TJ') + + ax4.set_xlabel('Completion Time (years)', fontsize=12) + ax4.set_ylabel('Average Energy per Launch (TJ)', fontsize=12) + ax4.set_title('Energy Efficiency vs Timeline\n(Longer timeline → more low-lat launches)', fontsize=14) + ax4.legend() + ax4.grid(True, alpha=0.3) + + plt.tight_layout() + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"\n图表已保存至: {save_path}") + + return df, fig + + +def plot_launch_distribution( + years: float, + save_path: str = '/Volumes/Files/code/mm/20260130_b/launch_distribution.png' +): + """ + 绘制特定年限下的发射分配详情 + """ + total_launches = int(TOTAL_PAYLOAD / PAYLOAD_PER_LAUNCH) + distribution = calculate_launch_distribution(total_launches, years) + stats = calculate_total_energy(distribution) + + fig, axes = plt.subplots(1, 2, figsize=(14, 6)) + + # ========== 图1: 发射次数分配 ========== + ax1 = axes[0] + sites = [s.short_name for s, _ in distribution] + launches = [n for _, n in distribution] + latitudes = [s.abs_latitude for s, _ in distribution] + + colors = plt.cm.RdYlGn_r(np.array(latitudes) / 60) + bars = ax1.barh(sites, launches, color=colors) + + ax1.set_xlabel('Number of Launches', fontsize=12) + ax1.set_ylabel('Launch Site', fontsize=12) + ax1.set_title(f'Launch Distribution ({years:.1f} years completion)\n' + f'Total: {total_launches:,} launches', fontsize=13) + ax1.grid(True, alpha=0.3, axis='x') + + # 添加数值标签 + for bar, n in zip(bars, launches): + if n > 0: + ax1.text(bar.get_width() + 1000, bar.get_y() + bar.get_height()/2, + f'{n:,}', va='center', fontsize=9) + + # ========== 图2: 能量消耗分配 ========== + ax2 = axes[1] + + site_names = [d['short_name'] for d in stats['sites']] + energies = [d['energy_PJ'] for d in stats['sites']] + + colors = plt.cm.RdYlGn_r(np.array([d['latitude'] for d in stats['sites']]) / 60) + bars = ax2.barh(site_names, energies, color=colors) + + ax2.set_xlabel('Energy Consumption (PJ)', fontsize=12) + ax2.set_ylabel('Launch Site', fontsize=12) + ax2.set_title(f'Energy Distribution\n' + f'Total: {stats["total_energy_PJ"]:.1f} PJ', fontsize=13) + ax2.grid(True, alpha=0.3, axis='x') + + # 添加数值标签 + for bar, e in zip(bars, energies): + if e > 0: + ax2.text(bar.get_width() + 0.5, bar.get_y() + bar.get_height()/2, + f'{e:.1f}', va='center', fontsize=9) + + plt.tight_layout() + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"分配详情图已保存至: {save_path}") + + return distribution, stats + + +def print_analysis_summary(df: pd.DataFrame): + """打印分析摘要""" + print("\n" + "=" * 80) + print("ROCKET LAUNCH SCENARIO ANALYSIS SUMMARY") + print("=" * 80) + + total_launches = int(TOTAL_PAYLOAD / PAYLOAD_PER_LAUNCH) + + print(f"\nMission Parameters:") + print(f" - Total payload: {TOTAL_PAYLOAD/1e6:.0f} million metric tons") + print(f" - Payload per launch: {PAYLOAD_PER_LAUNCH} metric tons") + print(f" - Total launches required: {total_launches:,}") + print(f" - Number of launch sites: {len(LAUNCH_SITES)}") + + # 最短时间场景 + min_years = df['years'].min() + min_time_row = df[df['years'] == min_years].iloc[0] + print(f"\nMinimum Time Scenario ({min_years:.1f} years):") + print(f" - All {int(min_time_row['sites_used'])} sites at full capacity") + print(f" - Total energy: {min_time_row['total_energy_PJ']:.1f} PJ") + print(f" - Total fuel: {min_time_row['total_fuel_Mt']:.1f} million metric tons") + print(f" - Low-latitude ratio: {min_time_row['low_lat_ratio']*100:.1f}%") + + # 最优能量场景 (只使用最低纬度站点) + max_years = df['years'].max() + max_time_row = df[df['years'] == max_years].iloc[0] + print(f"\nExtended Timeline Scenario ({max_years:.1f} years):") + print(f" - Using {int(max_time_row['sites_used'])} lowest-latitude sites") + print(f" - Total energy: {max_time_row['total_energy_PJ']:.1f} PJ") + print(f" - Total fuel: {max_time_row['total_fuel_Mt']:.1f} million metric tons") + print(f" - Low-latitude ratio: {max_time_row['low_lat_ratio']*100:.1f}%") + + # 能量节省 + energy_saving = (1 - max_time_row['total_energy_PJ'] / min_time_row['total_energy_PJ']) * 100 + print(f"\nEnergy Savings (Extended vs Minimum time):") + print(f" - Energy reduction: {energy_saving:.1f}%") + print(f" - Time trade-off: {max_years - min_years:.1f} additional years") + + print("\n" + "=" * 80) + print("Launch Site Details:") + print("=" * 80) + print(f"\n{'Site':<25} {'Latitude':>10} {'ΔV Loss':>10} {'Fuel Ratio':>12} {'Energy/Launch':>14}") + print(f"{'':25} {'(°)':>10} {'(m/s)':>10} {'':>12} {'(TJ)':>14}") + print("-" * 80) + + for site in LAUNCH_SITES: + k = fuel_ratio_multistage(site.total_delta_v) + e = energy_per_launch(site) / 1e12 + print(f"{site.name:<25} {site.abs_latitude:>10.1f} {site.delta_v_loss:>10.0f} {k:>12.1f} {e:>14.1f}") + + print("=" * 80) + + +def analyze_launch_frequency_scenarios( + save_path: str = '/Volumes/Files/code/mm/20260130_b/launch_frequency_analysis.png' +): + """ + 分析不同发射频率场景下的完成时间和能量消耗 + """ + total_launches = int(TOTAL_PAYLOAD / PAYLOAD_PER_LAUNCH) + num_sites = len(LAUNCH_SITES) + + # 不同发射频率场景 + frequencies = [1, 2, 4, 8, 12, 24] # launches per day per site + + results = [] + for freq in frequencies: + # 最短完成时间 + min_years = total_launches / (num_sites * freq * 365) + + # 分析该时间下的能量(所有站点满负荷) + distribution = calculate_launch_distribution(total_launches, min_years) + stats = calculate_total_energy(distribution) + + results.append({ + 'frequency': freq, + 'min_years': min_years, + 'total_energy_PJ': stats['total_energy_PJ'], + 'total_fuel_Mt': stats['total_fuel_Mt'], + }) + + df = pd.DataFrame(results) + + # 绘图 + fig, axes = plt.subplots(1, 2, figsize=(14, 6)) + + # 图1: 发射频率 vs 完成时间 + ax1 = axes[0] + ax1.bar(range(len(frequencies)), df['min_years'], color='steelblue', alpha=0.8) + ax1.set_xticks(range(len(frequencies))) + ax1.set_xticklabels([f'{f}/day' for f in frequencies]) + ax1.set_xlabel('Launch Frequency (per site)', fontsize=12) + ax1.set_ylabel('Minimum Completion Time (years)', fontsize=12) + ax1.set_title('Completion Time vs Launch Frequency\n(10 sites, 800,000 launches)', fontsize=13) + ax1.grid(True, alpha=0.3, axis='y') + + # 添加数值标签 + for i, (freq, years) in enumerate(zip(frequencies, df['min_years'])): + ax1.text(i, years + 2, f'{years:.1f}y', ha='center', fontsize=10, fontweight='bold') + + # 图2: 能量消耗比较 + ax2 = axes[1] + ax2.bar(range(len(frequencies)), df['total_energy_PJ'], color='coral', alpha=0.8) + ax2.set_xticks(range(len(frequencies))) + ax2.set_xticklabels([f'{f}/day' for f in frequencies]) + ax2.set_xlabel('Launch Frequency (per site)', fontsize=12) + ax2.set_ylabel('Total Energy (PJ)', fontsize=12) + ax2.set_title('Total Energy Consumption\n(Same for all frequencies at max capacity)', fontsize=13) + ax2.grid(True, alpha=0.3, axis='y') + + plt.tight_layout() + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"\n发射频率分析图已保存至: {save_path}") + + return df + + +def plot_comprehensive_analysis( + save_path: str = '/Volumes/Files/code/mm/20260130_b/rocket_comprehensive.png' +): + """ + 综合分析图:不同时间线下的能量消耗和发射分配 + """ + total_launches = int(TOTAL_PAYLOAD / PAYLOAD_PER_LAUNCH) + num_sites = len(LAUNCH_SITES) + min_years = total_launches / (num_sites * 365) + + # 选择几个关键时间点 + key_years = [min_years, min_years*1.5, min_years*2, min_years*3, min_years*5] + + fig, axes = plt.subplots(2, 3, figsize=(18, 12)) + + # 第一行:能量趋势 + ax_main = axes[0, 0] + years_range = np.linspace(min_years, min_years * 5, 100) + df = analyze_completion_timeline(years_range) + + ax_main.plot(df['years'], df['total_energy_PJ'], 'b-', linewidth=2) + ax_main.fill_between(df['years'], df['total_energy_PJ'], alpha=0.3) + + # 标记关键点 + for y in key_years[1:]: + ax_main.axvline(x=y, color='gray', linestyle='--', alpha=0.5) + + ax_main.set_xlabel('Completion Time (years)', fontsize=11) + ax_main.set_ylabel('Total Energy (PJ)', fontsize=11) + ax_main.set_title('Energy vs Timeline\n(Longer time → Lower energy)', fontsize=12) + ax_main.grid(True, alpha=0.3) + + # 能量效率 + ax_eff = axes[0, 1] + ax_eff.plot(df['years'], df['energy_per_launch_TJ'], 'r-', linewidth=2) + ax_eff.set_xlabel('Completion Time (years)', fontsize=11) + ax_eff.set_ylabel('Energy per Launch (TJ)', fontsize=11) + ax_eff.set_title('Average Energy Efficiency', fontsize=12) + ax_eff.grid(True, alpha=0.3) + + # 发射场使用数量 + ax_sites = axes[0, 2] + ax_sites.plot(df['years'], df['sites_used'], 'g-', linewidth=2) + ax_sites.set_xlabel('Completion Time (years)', fontsize=11) + ax_sites.set_ylabel('Sites Used', fontsize=11) + ax_sites.set_title('Number of Active Sites', fontsize=12) + ax_sites.set_ylim(0, 11) + ax_sites.grid(True, alpha=0.3) + + # 第二行:三个时间点的发射分配饼图 + scenario_years = [min_years, min_years*2, min_years*5] + scenario_names = ['Minimum Time', '2x Minimum', '5x Minimum'] + + for idx, (years, name) in enumerate(zip(scenario_years, scenario_names)): + ax = axes[1, idx] + distribution = calculate_launch_distribution(total_launches, years) + + # 只显示有发射的站点 + labels = [s.short_name for s, n in distribution if n > 0] + sizes = [n for _, n in distribution if n > 0] + latitudes = [s.abs_latitude for s, n in distribution if n > 0] + + colors = plt.cm.RdYlGn_r(np.array(latitudes) / 60) + + wedges, texts, autotexts = ax.pie(sizes, labels=labels, autopct='%1.0f%%', + colors=colors, pctdistance=0.75) + + # 计算该场景的能量 + stats = calculate_total_energy(distribution) + + ax.set_title(f'{name}\n({years:.0f} years, {stats["total_energy_PJ"]:.0f} PJ)', + fontsize=11) + + plt.suptitle(f'Rocket Launch Scenario: {TOTAL_PAYLOAD/1e6:.0f}M tons to Moon\n' + f'({total_launches:,} launches needed)', fontsize=14, y=1.02) + + plt.tight_layout() + plt.savefig(save_path, dpi=150, bbox_inches='tight') + print(f"\n综合分析图已保存至: {save_path}") + + return fig + + +# ============== 主程序 ============== + +if __name__ == "__main__": + print("=" * 80) + print("MOON COLONY ROCKET LAUNCH SCENARIO ANALYSIS") + print("Mission: Deliver 100 million metric tons to Moon") + print("Constraint: 10 launch sites, max 1 launch/day each") + print("=" * 80) + + # 运行时间线分析 + df, fig = plot_timeline_analysis() + + # 打印摘要 + print_analysis_summary(df) + + # 绘制综合分析图 + plot_comprehensive_analysis() + + # 发射频率场景分析 + print("\n" + "=" * 80) + print("LAUNCH FREQUENCY SENSITIVITY ANALYSIS") + print("=" * 80) + freq_df = analyze_launch_frequency_scenarios() + print("\nIf launch frequency can be increased:") + print(f"{'Frequency':>15} {'Min Years':>15} {'Energy (PJ)':>15}") + print("-" * 50) + for _, row in freq_df.iterrows(): + print(f"{row['frequency']:>12}/day {row['min_years']:>15.1f} {row['total_energy_PJ']:>15.0f}") + + # 关键结论 + print("\n" + "=" * 80) + print("KEY FINDINGS") + print("=" * 80) + print(f""" + 1. MINIMUM COMPLETION TIME: {df['years'].min():.0f} years + (All 10 sites at 1 launch/day capacity) + + 2. TOTAL ENERGY REQUIRED: ~{df['total_energy_PJ'].mean():.0f} PJ + (~{df['total_fuel_Mt'].mean():.0f} million metric tons of fuel) + + 3. LATITUDE EFFECT: + - Energy savings from using only low-lat sites: ~2-3% + - Trade-off: Significantly longer timeline + + 4. CONCLUSION: + Traditional rockets alone are IMPRACTICAL for this mission. + 219+ years is far beyond reasonable project timeline. + Space Elevator System is essential for feasibility. + """) diff --git a/rocket_launch_timeline.png b/rocket_launch_timeline.png new file mode 100644 index 0000000..64a8a46 Binary files /dev/null and b/rocket_launch_timeline.png differ diff --git a/rocket_launch_to_csv.py b/rocket_launch_to_csv.py new file mode 100644 index 0000000..44aa784 --- /dev/null +++ b/rocket_launch_to_csv.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""将 rocket launch counts.txt 数据导出为 CSV 文件""" + +import csv +import re + + +def parse_header(header_line: str) -> list[str]: + """从 # 开头的表头行解析列名""" + # 去掉开头的 # 和多余空格,按空白分割 + clean = header_line.lstrip("#").strip() + # 列名之间可能有多空格,统一按空白分割 + return re.split(r"\s{2,}", clean) + + +def parse_data_line(line: str) -> list[str]: + """解析数据行,按空白分割""" + return line.split() + + +def main(): + input_file = "rocket launch counts.txt" + output_file = "rocket_launch_counts.csv" + + headers = None + rows = [] + + with open(input_file, "r", encoding="utf-8") as f: + for line in f: + line = line.rstrip("\n") + if not line.strip(): + continue + if line.startswith("#"): + # 第二行是分隔线,第一行是列名 + if "Bin" in line and "YDate" in line and headers is None: + headers = parse_header(line) + continue + # 数据行 + values = parse_data_line(line) + if len(values) >= 4: # 至少包含 Bin, YDate, ValMin, ValMax + rows.append(values) + + # 如果没有成功解析表头,使用默认列名 + if headers is None or len(headers) != len(rows[0]) if rows else True: + headers = [ + "Bin", "YDate", "ValMin", "ValMax", + "US", "SU", "RU", "CN", "F", "J", "AU", "D", "UK", "I", + "I-ELDO", "IN", "BR", "KR", "I-ESA", "KP", "IR", "IL", "CYM", "NZ", + "Total" + ] + + with open(output_file, "w", encoding="utf-8", newline="") as out: + writer = csv.writer(out) + writer.writerow(headers) + writer.writerows(rows) + + print(f"已导出 {len(rows)} 行数据到 {output_file}") + + +if __name__ == "__main__": + main() diff --git a/scenario_comparison.png b/scenario_comparison.png new file mode 100644 index 0000000..d75ecfb Binary files /dev/null and b/scenario_comparison.png differ diff --git a/three_scenarios_comparison.png b/three_scenarios_comparison.png new file mode 100644 index 0000000..ff8b9f3 Binary files /dev/null and b/three_scenarios_comparison.png differ