Files
2026_mcm_b/p1/combined_scenario.py

706 lines
28 KiB
Python
Raw Normal View History

2026-01-31 11:42:20 +08:00
"""
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
2026-02-02 14:32:39 +08:00
ISP = 363 # 比冲 (秒) - 液氧甲烷 (LOX/CH4, Raptor-class)
SPECIFIC_FUEL_ENERGY = 11.9e6 # J/kg
2026-01-31 11:42:20 +08:00
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)