This commit is contained in:
2026-01-31 11:49:38 +08:00
parent 839ad8c956
commit d534a49973
26 changed files with 0 additions and 0 deletions

BIN
p1/.DS_Store vendored Normal file

Binary file not shown.

357
p1/README.md Normal file
View 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`**
混合方案(电梯优先 + 火箭补齐),输出组合方案能量-工期曲线,并生成 **100300 年**区间三方案同图对比。
- **`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`** | **100300 年区间**:纯火箭、纯电梯、混合方案的“年份-能量”折线同图对比 |
| `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`
![specific_energy_comparison](specific_energy_comparison.png)
#### `latitude_effects.png`
![latitude_effects](latitude_effects.png)
</details>
<details>
<summary><strong>2) 纯火箭方案(排布与敏感性)</strong></summary>
#### `rocket_launch_timeline.png`
![rocket_launch_timeline](rocket_launch_timeline.png)
#### `rocket_comprehensive.png`
![rocket_comprehensive](rocket_comprehensive.png)
#### `launch_distribution_min.png`
![launch_distribution_min](launch_distribution_min.png)
#### `launch_distribution_mid.png`
![launch_distribution_mid](launch_distribution_mid.png)
#### `launch_frequency_analysis.png`
![launch_frequency_analysis](launch_frequency_analysis.png)
</details>
<details>
<summary><strong>3) 混合方案与三方案对比(核心结论图)</strong></summary>
#### `combined_scenario_analysis.png`
![combined_scenario_analysis](combined_scenario_analysis.png)
#### `scenario_comparison.png`
![scenario_comparison](scenario_comparison.png)
#### `three_scenarios_comparison.png`100300 年折线同图对比)
![three_scenarios_comparison](three_scenarios_comparison.png)
</details>
<details>
<summary><strong>4)(可选)火箭发射能力上限/趋势拟合</strong></summary>
#### `launch_capacity_analysis.png`
![launch_capacity_analysis](launch_capacity_analysis.png)
#### `launch_capacity_physical.png`
![launch_capacity_physical](launch_capacity_physical.png)
#### `launch_capacity_predictions.png`
![launch_capacity_predictions](launch_capacity_predictions.png)
</details>
<details>
<summary><strong>5) 其它历史遗留图</strong></summary>
本目录还包含 `earth_moon_transfer_analysis.png`(早期“地月转移:载荷–燃料/能量关系”图)。当前目录下未发现对应的 `earth_moon_transfer.py` 源码文件;如果你需要我把该脚本补回并与现有口径统一,我可以再加一个文件。
#### `earth_moon_transfer_analysis.png`
![earth_moon_transfer_analysis](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混合方案 + 100300 年三方案同图对比
```bash
python combined_scenario.py
```
会生成:
- `combined_scenario_analysis.png`
- `scenario_comparison.png`
- **`three_scenarios_comparison.png`100300 年折线同图对比)**
**部分运行输出(示例)**
```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 GuianaKourou | 5.2° | 低纬优势明显 |
| Satish DhawanIndia | 13.7° | 低纬 |
| TexasBoca Chica | 26.0° | 美国 |
| FloridaCape Canaveral | 28.5° | 美国 |
| CaliforniaVandenberg | 34.7° | 美国 |
| VirginiaWallops | 37.8° | 美国 |
| TaiyuanChina | 38.8° | 中国 |
| MahiaNew Zealand | 39.3° | 新西兰(南半球,按 \(|\varphi|\) 处理) |
| KazakhstanBaikonur | 45.6° | 哈萨克斯坦 |
| AlaskaKodiak | 57.4° | 高纬,惩罚最大 |
对应曲线可在下图直观看到:
![latitude_effects](latitude_effects.png)
---
### 三方案“年份能量”折线对比100300 年)
下图是你要求的 **100300 年**区间三条折线同图(不可完成的年份用空缺表示):
![three_scenarios_comparison](three_scenarios_comparison.png)
读图要点:
- **100186 年**:只有混合方案可完成(电梯能力不足、火箭也不够快)。
- **186219 年**:电梯可完成;火箭仍无法完成;混合与电梯曲线重合(优先用电梯)。
- **≥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`:赤道基准 ΔVm/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
p1/combined_scenario.py Normal file
View 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

BIN
p1/latitude_effects.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

View 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"],
"": 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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View File

@@ -0,0 +1,13 @@
数据窗口,模型,饱和上限K,拐点年份,拐点发射数,,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
1 数据窗口 模型 饱和上限K 拐点年份 拐点发射数 RMSE
2 全部数据 Logistic 405.92414403335295 2099.062819804582 202.96207201667647 0.07766486971853037 46.74814967377199
3 全部数据 Gompertz 401.0329954671314 2051.6490738033563 147.53179426375786 0.07545958812791254 46.80400317046386
4 全部数据 Richards 687.2209173212347 2256.999921701128 512.1739060128197 0.08220581306057739 46.632929826767096
5 近25年 Logistic 499998.291933841 2107.433566292766 249999.1459669205 0.8047914079948382 29.605437976518306
6 近25年 Gompertz 499999.9999896808 2216.1161350271577 183939.72058192495 0.779423273804876 31.47037146055304
7 近25年 Richards 16788.895636148314 2070.4580199940424 10907.704452410562 0.8048416215235632 29.60163002650332
8 近15年 Logistic 499999.9999933337 2082.166434872205 249999.99999666685 0.8979462268050302 23.639528906752357
9 近15年 Gompertz 499999.9999999759 2153.207304429069 183939.72058571232 0.8798702599574121 25.647766611285153
10 近15年 Richards 32126.622670684326 2061.1007442806736 20365.326256859138 0.8979860420054704 23.63491710078375
11 近10年 Logistic 499999.9996950232 2069.5299620988108 249999.9998475116 0.9632290653308331 15.01250946110567
12 近10年 Gompertz 499999.99999999994 2122.2979572477343 183939.72058572114 0.9542285701514692 16.74935931143396
13 近10年 Richards 23814.980027911413 2051.157710362777 15373.812459292129 0.9632499410027958 15.00824738946148

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

24
p1/launch_sites.csv Normal file
View 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,
1 英文名 发射场 发射场所在国家 2023年 2024年 2025年
2 Florida 卡角太空基地(卡纳维拉尔角空军基地) 美国 59 71 82
3 California 范登堡太空基地(范登堡旧金山基地) 美国 30 46 66
4 Florida 肯尼迪航天中心 美国 13 23 27
5 Virginia 沃洛普斯飞行中心 美国 3 1
6 Texas 博卡奇卡星星基地 美国 2 4
7 酒泉卫星发射场 中国 36 21 33
8 西昌卫星发射场 中国 15 19 18
9 Taiyuan Satellite Launch Center (China) 太原卫星发射场 中国 4 13
10 文昌航天发射场 中国 9 8
11 海南商业航天发射场 中国 0 1
12 广东阳江 中国 2 2
13 山东海阳 中国 1 4
14 普列谢茨克航天发射场 俄罗斯 7 5
15 东方航天发射场 俄罗斯 3 4
16 Kazakhstan 拜科努尔航天发射场 哈萨克斯坦 9 8
17 Mahia Peninsula (New Zealand) 欧尼努伊站 新西兰 7 13
18 Satish Dhawan Space Centre (India) 萨迪什·达万航天中心 印度 7 5
19 French Guiana 圭亚那航天中心 法国 3 3
20 种子岛航天中心 日本 3 5
21 纪伊航天港 日本 0 2
22 西海卫星发射场 朝鲜 3 1
23 沙赫鲁德航天中心 伊朗 2 2
24 塞姆南航天中心 伊朗 0 2

View 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
p1/rocket_comprehensive.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

View 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
1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
13 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
14 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
15 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
16 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
17 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
18 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
19 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
20 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
21 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
22 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
23 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
24 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
25 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
26 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
27 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
28 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
29 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
30 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
31 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
32 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
33 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
34 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
35 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
36 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
37 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
38 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
39 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
40 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
41 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
42 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
43 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
44 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
45 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
46 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
47 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
48 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
49 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
50 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
51 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
52 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
53 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
54 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
55 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
56 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
57 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
58 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
59 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
60 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
61 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
62 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
63 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
64 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
65 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
66 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
67 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
68 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
69 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
70 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
71 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
72 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
73 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

View 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.
""")

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

View 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
p1/scenario_comparison.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB