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

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()