p1
357
README.md
Normal file
@@ -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`**。
|
||||
|
||||
<details>
|
||||
<summary><strong>1) 比能量与纬度效应(地面 vs 电梯、发射场纬度)</strong></summary>
|
||||
|
||||
#### `specific_energy_comparison.png`
|
||||
|
||||

|
||||
|
||||
#### `latitude_effects.png`
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>2) 纯火箭方案(排布与敏感性)</strong></summary>
|
||||
|
||||
#### `rocket_launch_timeline.png`
|
||||
|
||||

|
||||
|
||||
#### `rocket_comprehensive.png`
|
||||
|
||||

|
||||
|
||||
#### `launch_distribution_min.png`
|
||||
|
||||

|
||||
|
||||
#### `launch_distribution_mid.png`
|
||||
|
||||

|
||||
|
||||
#### `launch_frequency_analysis.png`
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>3) 混合方案与三方案对比(核心结论图)</strong></summary>
|
||||
|
||||
#### `combined_scenario_analysis.png`
|
||||
|
||||

|
||||
|
||||
#### `scenario_comparison.png`
|
||||
|
||||

|
||||
|
||||
#### `three_scenarios_comparison.png`(100–300 年折线同图对比)
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>4)(可选)火箭发射能力上限/趋势拟合</strong></summary>
|
||||
|
||||
#### `launch_capacity_analysis.png`
|
||||
|
||||

|
||||
|
||||
#### `launch_capacity_physical.png`
|
||||
|
||||

|
||||
|
||||
#### `launch_capacity_predictions.png`
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>5) 其它历史遗留图</strong></summary>
|
||||
|
||||
本目录还包含 `earth_moon_transfer_analysis.png`(早期“地月转移:载荷–燃料/能量关系”图)。当前目录下未发现对应的 `earth_moon_transfer.py` 源码文件;如果你需要我把该脚本补回并与现有口径统一,我可以再加一个文件。
|
||||
|
||||
#### `earth_moon_transfer_analysis.png`
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
### 依赖安装
|
||||
|
||||
建议使用 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° | 高纬,惩罚最大 |
|
||||
|
||||
对应曲线可在下图直观看到:
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### 三方案“年份–能量”折线对比(100–300 年)
|
||||
|
||||
下图是你要求的 **100–300 年**区间三条折线同图(不可完成的年份用空缺表示):
|
||||
|
||||

|
||||
|
||||
读图要点:
|
||||
- **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 年**,能耗介于两者之间,并随着工期增加逐步趋近纯电梯能耗。
|
||||
|
||||
705
combined_scenario.py
Normal file
@@ -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)
|
||||
BIN
combined_scenario_analysis.png
Normal file
|
After Width: | Height: | Size: 337 KiB |
@@ -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")
|
||||
BIN
launch_capacity_analysis.png
Normal file
|
After Width: | Height: | Size: 248 KiB |
785
launch_capacity_analysis.py
Normal file
@@ -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()
|
||||
BIN
launch_capacity_physical.png
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
launch_capacity_predictions.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
13
launch_capacity_results.csv
Normal file
@@ -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
|
||||
|
BIN
launch_distribution_mid.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
launch_distribution_min.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
launch_frequency_analysis.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
24
launch_sites.csv
Normal file
@@ -0,0 +1,24 @@
|
||||
英文名,发射场,发射场所在国家,2023年,2024年,2025年
|
||||
Florida,卡角太空基地(卡纳维拉尔角空军基地),美国,59,71,82
|
||||
California,范登堡太空基地(范登堡旧金山基地),美国,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,
|
||||
,文昌航天发射场,中国,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,
|
||||
|
78
rocket launch counts.txt
Normal file
@@ -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
|
||||
BIN
rocket_comprehensive.png
Normal file
|
After Width: | Height: | Size: 251 KiB |
73
rocket_launch_counts.csv
Normal file
@@ -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
|
||||
|
678
rocket_launch_scenario.py
Normal file
@@ -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.
|
||||
""")
|
||||
BIN
rocket_launch_timeline.png
Normal file
|
After Width: | Height: | Size: 230 KiB |
61
rocket_launch_to_csv.py
Normal file
@@ -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()
|
||||
BIN
scenario_comparison.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
three_scenarios_comparison.png
Normal file
|
After Width: | Height: | Size: 139 KiB |