#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
火箭发射数上限预测分析
对比 Logistic、Gompertz、Richards 三种增长模型
分析不同历史数据窗口对预测的影响
"""

import pandas as pd
import numpy as np
from scipy.optimize import curve_fit
from scipy.stats import pearsonr
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')

plt.rcParams["font.sans-serif"] = ["Arial Unicode MS", "SimHei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False


# ============================================================
# 1. 增长模型定义
# ============================================================

def logistic(t, K, r, t0):
    """
    Logistic 增长模型（对称S曲线）
    N(t) = K / (1 + exp(-r*(t - t0)))
    
    参数:
        K  : 环境容量/饱和上限
        r  : 增长率
        t0 : 拐点时间（增长最快的时刻，此时 N = K/2）
    """
    return K / (1 + np.exp(-r * (t - t0)))


def gompertz(t, K, b, c):
    """
    Gompertz 增长模型（非对称S曲线，早期增长较快）
    N(t) = K * exp(-b * exp(-c*t))
    
    参数:
        K : 饱和上限
        b : 位移参数
        c : 增长率参数
    拐点在 N = K/e ≈ 0.368K 处
    """
    return K * np.exp(-b * np.exp(-c * t))


def richards(t, K, r, t0, v):
    """
    Richards 曲线（广义 Logistic，可调节对称性）
    N(t) = K / (1 + exp(-r*(t - t0)))^(1/v)
    
    参数:
        K  : 饱和上限
        r  : 增长率
        t0 : 拐点时间
        v  : 形状参数（v=1 时退化为 Logistic）
    """
    exp_term = np.exp(-r * (t - t0))
    # 避免数值溢出
    exp_term = np.clip(exp_term, 1e-10, 1e10)
    return K / np.power(1 + exp_term, 1/v)


# ============================================================
# 2. 数据加载与预处理
# ============================================================

def load_data(filepath="rocket_launch_counts.csv"):
    """加载并清洗火箭发射数据"""
    df = pd.read_csv(filepath)
    df = df.rename(columns={"YDate": "year", "Total": "launches"})
    df["year"] = pd.to_numeric(df["year"], errors="coerce")
    df["launches"] = pd.to_numeric(df["launches"], errors="coerce")
    df = df.dropna(subset=["year", "launches"])
    # 只保留完整年份数据（截至2025年，2026年数据不完整）
    df = df[(df["year"] >= 1957) & (df["year"] <= 2025)]
    df = df.astype({"year": int, "launches": int})
    df = df.sort_values("year").reset_index(drop=True)
    return df


# ============================================================
# 3. 模型拟合
# ============================================================

def fit_model(model_func, t, y, model_name, p0, bounds):
    """通用模型拟合函数"""
    try:
        popt, pcov = curve_fit(model_func, t, y, p0=p0, bounds=bounds, maxfev=50000)
        perr = np.sqrt(np.diag(pcov))
        y_pred = model_func(t, *popt)
        
        # 计算拟合优度
        ss_res = np.sum((y - y_pred) ** 2)
        ss_tot = np.sum((y - np.mean(y)) ** 2)
        r_squared = 1 - (ss_res / ss_tot)
        rmse = np.sqrt(np.mean((y - y_pred) ** 2))
        
        return {
            "success": True,
            "params": popt,
            "param_err": perr,
            "r_squared": r_squared,
            "rmse": rmse,
            "y_pred": y_pred
        }
    except Exception as e:
        return {
            "success": False,
            "error": str(e)
        }


def fit_all_models(data, base_year=1957):
    """对数据拟合所有三种模型"""
    years = data["year"].values
    launches = data["launches"].values
    t = (years - base_year).astype(float)
    
    results = {}
    
    # Logistic 模型 - 提高上限边界以避免撞边界
    p0_log = [2000.0, 0.08, 70.0]
    bounds_log = ([300, 0.01, 0], [500000, 1.0, 300])
    res = fit_model(logistic, t, launches, "Logistic", p0_log, bounds_log)
    if res["success"]:
        K, r, t0 = res["params"]
        res["K"] = K
        res["inflection_year"] = base_year + t0
        res["inflection_value"] = K / 2
    results["Logistic"] = res
    
    # Gompertz 模型
    p0_gom = [2000.0, 5.0, 0.03]
    bounds_gom = ([300, 0.5, 0.005], [500000, 50.0, 0.5])
    res = fit_model(gompertz, t, launches, "Gompertz", p0_gom, bounds_gom)
    if res["success"]:
        K, b, c = res["params"]
        res["K"] = K
        # Gompertz 拐点：t = ln(b)/c, 值为 K/e
        res["inflection_year"] = base_year + np.log(b) / c
        res["inflection_value"] = K / np.e
    results["Gompertz"] = res
    
    # Richards 模型
    p0_ric = [2000.0, 0.08, 70.0, 2.0]
    bounds_ric = ([300, 0.01, 0, 0.1], [500000, 1.0, 300, 10.0])
    res = fit_model(richards, t, launches, "Richards", p0_ric, bounds_ric)
    if res["success"]:
        K, r, t0, v = res["params"]
        res["K"] = K
        res["inflection_year"] = base_year + t0
        res["inflection_value"] = K / (v + 1) ** (1/v)
        res["v"] = v
    results["Richards"] = res
    
    return results, years, launches, t, base_year


def predict_future(results, base_year, years_future):
    """生成未来预测"""
    t_future = years_future - base_year
    predictions = {}
    
    for model_name, res in results.items():
        if not res["success"]:
            continue
        
        params = res["params"]
        if model_name == "Logistic":
            pred = logistic(t_future, *params)
        elif model_name == "Gompertz":
            pred = gompertz(t_future, *params)
        elif model_name == "Richards":
            pred = richards(t_future, *params)
        
        predictions[model_name] = np.maximum(pred, 0)
    
    return predictions


# ============================================================
# 4. 可视化
# ============================================================

def plot_model_comparison(df, results_dict, save_path="launch_capacity_analysis.png"):
    """绘制模型对比图"""
    fig, axes = plt.subplots(2, 2, figsize=(16, 12))
    
    # 颜色方案
    colors = {
        "Logistic": "#E74C3C",
        "Gompertz": "#3498DB", 
        "Richards": "#27AE60"
    }
    
    # 子图1: 历史数据 + 三种模型拟合（全部数据）
    ax1 = axes[0, 0]
    results, years, launches, t, base_year = results_dict["全部数据"]
    ax1.scatter(years, launches, color="gray", s=30, alpha=0.7, label="历史数据", zorder=3)
    
    years_curve = np.arange(years.min(), 2101)
    t_curve = years_curve - base_year
    
    for model_name, res in results.items():
        if not res["success"]:
            continue
        params = res["params"]
        if model_name == "Logistic":
            pred = logistic(t_curve, *params)
        elif model_name == "Gompertz":
            pred = gompertz(t_curve, *params)
        elif model_name == "Richards":
            pred = richards(t_curve, *params)
        
        ax1.plot(years_curve, pred, color=colors[model_name], lw=2,
                label=f"{model_name} (K={res['K']:.0f}, R²={res['r_squared']:.3f})")
    
    ax1.axvline(2025, color="gray", ls="--", lw=1, alpha=0.5)
    ax1.set_xlabel("年份", fontsize=11)
    ax1.set_ylabel("年发射次数", fontsize=11)
    ax1.set_title("全部历史数据 (1957-2025) 模型拟合", fontsize=12)
    ax1.legend(loc="upper left", fontsize=9)
    ax1.grid(True, alpha=0.3)
    ax1.set_xlim(1955, 2105)
    ax1.set_ylim(0, None)
    
    # 子图2: 近15年数据拟合
    ax2 = axes[0, 1]
    results, years, launches, t, base_year = results_dict["近15年"]
    ax2.scatter(years, launches, color="gray", s=40, alpha=0.8, label="历史数据", zorder=3)
    
    years_curve = np.arange(2010, 2101)
    t_curve = years_curve - base_year
    
    for model_name, res in results.items():
        if not res["success"]:
            continue
        params = res["params"]
        if model_name == "Logistic":
            pred = logistic(t_curve, *params)
        elif model_name == "Gompertz":
            pred = gompertz(t_curve, *params)
        elif model_name == "Richards":
            pred = richards(t_curve, *params)
        
        ax2.plot(years_curve, pred, color=colors[model_name], lw=2,
                label=f"{model_name} (K={res['K']:.0f})")
    
    ax2.axvline(2025, color="gray", ls="--", lw=1, alpha=0.5)
    ax2.set_xlabel("年份", fontsize=11)
    ax2.set_ylabel("年发射次数", fontsize=11)
    ax2.set_title("近15年数据 (2010-2025) 模型拟合", fontsize=12)
    ax2.legend(loc="upper left", fontsize=9)
    ax2.grid(True, alpha=0.3)
    ax2.set_xlim(2008, 2105)
    ax2.set_ylim(0, None)
    
    # 子图3: 只展示 Richards 模型的K值（因为其他模型撞边界）
    ax3 = axes[1, 0]
    windows = ["全部数据", "近25年", "近15年", "近10年"]
    x = np.arange(len(windows))
    
    k_values = []
    for window in windows:
        if window in results_dict:
            res = results_dict[window][0].get("Richards", {})
            k_values.append(res.get("K", 0) if res.get("success", False) else 0)
        else:
            k_values.append(0)
    
    bars = ax3.bar(x, k_values, 0.6, color=colors["Richards"], alpha=0.8, edgecolor='black')
    
    # 添加数值标签
    for bar, k in zip(bars, k_values):
        if k > 0:
            ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 500, 
                    f'{k:.0f}', ha='center', va='bottom', fontsize=10, fontweight='bold')
    
    ax3.set_xlabel("数据窗口", fontsize=11)
    ax3.set_ylabel("预测饱和上限 K (次/年)", fontsize=11)
    ax3.set_title("Richards 模型: 不同数据窗口的饱和上限预测\n(Logistic/Gompertz 撞边界，不适用)", fontsize=11)
    ax3.set_xticks(x)
    ax3.set_xticklabels(windows)
    ax3.grid(True, alpha=0.3, axis='y')
    ax3.set_ylim(0, max(k_values) * 1.15 if k_values else 10000)
    
    # 子图4: Richards 模型拐点年份
    ax4 = axes[1, 1]
    inflection_years = []
    inflection_values = []
    for window in windows:
        if window in results_dict:
            res = results_dict[window][0].get("Richards", {})
            if res.get("success", False):
                inflection_years.append(res.get("inflection_year", 0))
                inflection_values.append(res.get("inflection_value", 0))
            else:
                inflection_years.append(0)
                inflection_values.append(0)
        else:
            inflection_years.append(0)
            inflection_values.append(0)
    
    bars = ax4.bar(x, inflection_years, 0.6, color=colors["Richards"], alpha=0.8, edgecolor='black')
    
    # 添加标签（拐点年份和对应发射数）
    for bar, year, val in zip(bars, inflection_years, inflection_values):
        if year > 0:
            ax4.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
                    f'{year:.0f}\n({val:.0f}次/年)', ha='center', va='bottom', fontsize=9)
    
    ax4.axhline(2025, color="red", ls="--", lw=2, alpha=0.8, label="当前年份 (2025)")
    ax4.set_xlabel("数据窗口", fontsize=11)
    ax4.set_ylabel("拐点年份", fontsize=11)
    ax4.set_title("Richards 模型: 预测拐点年份\n(增长最快的时刻)", fontsize=11)
    ax4.set_xticks(x)
    ax4.set_xticklabels(windows)
    ax4.legend(loc="lower right", fontsize=9)
    ax4.grid(True, alpha=0.3, axis='y')
    valid_years = [y for y in inflection_years if y > 0]
    if valid_years:
        ax4.set_ylim(2020, max(valid_years) + 15)
    
    plt.suptitle("火箭年发射数上限预测 - 多模型对比分析", fontsize=14, fontweight='bold', y=1.02)
    plt.tight_layout()
    plt.savefig(save_path, dpi=150, bbox_inches="tight")
    plt.close()
    print(f"图表已保存: {save_path}")


def plot_physical_vs_model(results_dict, physical_limits, save_path="launch_capacity_physical.png"):
    """绘制物理约束与模型预测对比图"""
    fig, axes = plt.subplots(1, 2, figsize=(14, 6))
    
    # 左图：物理约束与模型预测K值对比
    ax1 = axes[0]
    
    # 物理约束
    phys_scenarios = list(physical_limits.keys())
    phys_values = list(physical_limits.values())
    x_phys = np.arange(len(phys_scenarios))
    bars1 = ax1.barh(x_phys, phys_values, 0.6, color='#3498DB', alpha=0.7, label='物理约束估计')
    
    # 添加数值标签
    for bar, val in zip(bars1, phys_values):
        ax1.text(val + 50, bar.get_y() + bar.get_height()/2, 
                f'{val:,}', va='center', fontsize=10)
    
    # 添加模型预测（Richards）
    richards_k = []
    for window in ["近25年", "近15年", "近10年"]:
        if window in results_dict:
            res = results_dict[window][0].get("Richards", {})
            if res.get("success", False):
                richards_k.append(res["K"])
    
    if richards_k:
        avg_richards = np.mean(richards_k)
        ax1.axvline(avg_richards, color='#E74C3C', ls='--', lw=2, 
                   label=f'Richards模型平均 K={avg_richards:,.0f}')
    
    ax1.set_yticks(x_phys)
    ax1.set_yticklabels(phys_scenarios)
    ax1.set_xlabel("年发射上限 (次/年)", fontsize=11)
    ax1.set_title("物理约束 vs 模型预测 (10个发射场)", fontsize=12)
    ax1.legend(loc="lower right", fontsize=9)
    ax1.grid(True, alpha=0.3, axis='x')
    ax1.set_xlim(0, max(phys_values) * 1.3)
    
    # 右图：带物理约束的时间预测
    ax2 = axes[1]
    
    # 获取近15年Richards预测
    if "近15年" in results_dict:
        results, years, launches, t, base_year = results_dict["近15年"]
        res = results.get("Richards", {})
        
        if res["success"]:
            years_future = np.arange(2010, 2101)
            t_future = years_future - base_year
            pred = richards(t_future, *res["params"])
            
            ax2.scatter(years, launches, color="gray", s=40, alpha=0.7, label="历史数据", zorder=3)
            ax2.plot(years_future, pred, color='#27AE60', lw=2, label=f"Richards (K={res['K']:,.0f})")
            
            # 添加物理约束线
            phys_colors = ['#E74C3C', '#F39C12', '#3498DB', '#9B59B6', '#1ABC9C']
            for i, (scenario, limit) in enumerate(physical_limits.items()):
                ax2.axhline(limit, color=phys_colors[i % len(phys_colors)], 
                           ls='--', lw=1.5, alpha=0.7, label=f'{scenario}: {limit:,}')
            
            ax2.axvline(2025, color="gray", ls=":", lw=1, alpha=0.5)
            ax2.set_xlabel("年份", fontsize=11)
            ax2.set_ylabel("年发射次数", fontsize=11)
            ax2.set_title("Richards预测 + 物理约束线", fontsize=12)
            ax2.legend(loc="upper left", fontsize=8)
            ax2.grid(True, alpha=0.3)
            ax2.set_xlim(2008, 2105)
            ax2.set_ylim(0, max(physical_limits.values()) * 1.2)
    
    plt.suptitle("物理约束分析 (10个发射场)", fontsize=14, fontweight='bold', y=1.02)
    plt.tight_layout()
    plt.savefig(save_path, dpi=150, bbox_inches="tight")
    plt.close()
    print(f"图表已保存: {save_path}")


def plot_future_predictions(results_dict, save_path="launch_capacity_predictions.png"):
    """绘制未来预测图"""
    fig, axes = plt.subplots(1, 2, figsize=(16, 6))
    
    colors = {
        "Logistic": "#E74C3C",
        "Gompertz": "#3498DB", 
        "Richards": "#27AE60"
    }
    
    # 左图：基于全部数据的预测
    ax1 = axes[0]
    results, years, launches, t, base_year = results_dict["全部数据"]
    years_future = np.arange(1957, 2151)
    predictions = predict_future(results, base_year, years_future)
    
    ax1.scatter(years, launches, color="gray", s=20, alpha=0.6, label="历史数据", zorder=3)
    for model_name, pred in predictions.items():
        ax1.plot(years_future, pred, color=colors[model_name], lw=2, label=model_name)
    
    ax1.axvline(2025, color="gray", ls="--", lw=1, alpha=0.5)
    ax1.axvspan(2025, 2150, alpha=0.05, color="blue")
    ax1.set_xlabel("年份", fontsize=11)
    ax1.set_ylabel("年发射次数", fontsize=11)
    ax1.set_title("基于全部历史数据的预测 (至2150年)", fontsize=12)
    ax1.legend(loc="upper left", fontsize=10)
    ax1.grid(True, alpha=0.3)
    ax1.set_xlim(1955, 2155)
    ax1.set_ylim(0, None)
    
    # 右图：基于近15年数据的预测
    ax2 = axes[1]
    results, years, launches, t, base_year = results_dict["近15年"]
    years_future = np.arange(2010, 2151)
    predictions = predict_future(results, base_year, years_future)
    
    ax2.scatter(years, launches, color="gray", s=40, alpha=0.7, label="历史数据", zorder=3)
    for model_name, pred in predictions.items():
        ax2.plot(years_future, pred, color=colors[model_name], lw=2, label=model_name)
    
    ax2.axvline(2025, color="gray", ls="--", lw=1, alpha=0.5)
    ax2.axvspan(2025, 2150, alpha=0.05, color="blue")
    ax2.set_xlabel("年份", fontsize=11)
    ax2.set_ylabel("年发射次数", fontsize=11)
    ax2.set_title("基于近15年数据的预测 (至2150年)", fontsize=12)
    ax2.legend(loc="upper left", fontsize=10)
    ax2.grid(True, alpha=0.3)
    ax2.set_xlim(2008, 2155)
    ax2.set_ylim(0, None)
    
    plt.suptitle("火箭年发射数长期预测", fontsize=14, fontweight='bold', y=1.02)
    plt.tight_layout()
    plt.savefig(save_path, dpi=150, bbox_inches="tight")
    plt.close()
    print(f"图表已保存: {save_path}")


# ============================================================
# 5. 主程序
# ============================================================

def bootstrap_K_estimate(data, model_func, p0, bounds, n_bootstrap=500, base_year=1957):
    """Bootstrap 估计 K 值的不确定性"""
    years = data["year"].values
    launches = data["launches"].values
    t = (years - base_year).astype(float)
    n = len(t)
    
    K_samples = []
    for _ in range(n_bootstrap):
        # 重采样
        idx = np.random.choice(n, n, replace=True)
        t_boot = t[idx]
        y_boot = launches[idx]
        
        try:
            popt, _ = curve_fit(model_func, t_boot, y_boot, p0=p0, bounds=bounds, maxfev=10000)
            K_samples.append(popt[0])  # K 是第一个参数
        except:
            pass
    
    if len(K_samples) > 10:
        return {
            "mean": np.mean(K_samples),
            "median": np.median(K_samples),
            "std": np.std(K_samples),
            "ci_5": np.percentile(K_samples, 5),
            "ci_95": np.percentile(K_samples, 95),
            "samples": K_samples
        }
    return None


def calculate_aic_bic(y_true, y_pred, n_params):
    """计算 AIC 和 BIC"""
    n = len(y_true)
    ss_res = np.sum((y_true - y_pred) ** 2)
    mse = ss_res / n
    
    # Log-likelihood (假设高斯误差)
    log_likelihood = -n/2 * (np.log(2 * np.pi) + np.log(mse) + 1)
    
    aic = 2 * n_params - 2 * log_likelihood
    bic = n_params * np.log(n) - 2 * log_likelihood
    
    return aic, bic


def main():
    print("=" * 70)
    print("火箭年发射数上限预测分析")
    print("模型: Logistic, Gompertz, Richards")
    print("=" * 70)
    
    # 加载数据
    df = load_data()
    print(f"\n数据范围: {df['year'].min()} - {df['year'].max()}")
    print(f"数据点数: {len(df)}")
    print(f"最近5年发射数:")
    for _, row in df.tail(5).iterrows():
        print(f"  {int(row['year'])}: {int(row['launches'])} 次")
    
    # 计算增长率
    recent = df[df["year"] >= 2020].copy()
    if len(recent) > 1:
        growth_rates = recent["launches"].pct_change().dropna() * 100
        print(f"\n2020年以来年均增长率: {growth_rates.mean():.1f}%")
    
    # 定义不同的数据窗口
    end_year = 2025
    windows = {
        "全部数据": df[df["year"] <= end_year].copy(),
        "近25年": df[(df["year"] >= 2000) & (df["year"] <= end_year)].copy(),
        "近15年": df[(df["year"] >= 2010) & (df["year"] <= end_year)].copy(),
        "近10年": df[(df["year"] >= 2015) & (df["year"] <= end_year)].copy(),
    }
    
    # 存储所有结果
    all_results = {}
    
    # 对每个窗口进行拟合
    for window_name, data in windows.items():
        print(f"\n{'='*50}")
        print(f"数据窗口: {window_name} ({data['year'].min()}-{data['year'].max()}, n={len(data)})")
        print("=" * 50)
        
        results, years, launches, t, base_year = fit_all_models(data)
        all_results[window_name] = (results, years, launches, t, base_year)
        
        for model_name, res in results.items():
            if res["success"]:
                print(f"\n{model_name} 模型:")
                print(f"  饱和上限 K     = {res['K']:.0f} 次/年")
                print(f"  拐点年份       = {res['inflection_year']:.1f}")
                print(f"  拐点处发射数   = {res['inflection_value']:.0f} 次/年")
                print(f"  R² (拟合优度)  = {res['r_squared']:.4f}")
                print(f"  RMSE          = {res['rmse']:.2f}")
                if model_name == "Richards":
                    print(f"  形状参数 v     = {res.get('v', 'N/A'):.3f}")
            else:
                print(f"\n{model_name} 模型: 拟合失败 - {res.get('error', 'Unknown')}")
    
    # 生成未来预测表
    print("\n" + "=" * 70)
    print("未来预测 (基于近15年数据)")
    print("=" * 70)
    
    results, years, launches, t, base_year = all_results["近15年"]
    future_years = np.array([2030, 2040, 2050, 2075, 2100])
    predictions = predict_future(results, base_year, future_years)
    
    print(f"\n{'年份':<10}", end="")
    for model in ["Logistic", "Gompertz", "Richards"]:
        print(f"{model:<15}", end="")
    print()
    print("-" * 55)
    
    for i, year in enumerate(future_years):
        print(f"{year:<10}", end="")
        for model in ["Logistic", "Gompertz", "Richards"]:
            if model in predictions:
                print(f"{predictions[model][i]:<15.0f}", end="")
            else:
                print(f"{'N/A':<15}", end="")
        print()
    
    # 饱和上限汇总
    print("\n" + "=" * 70)
    print("饱和上限 K 汇总 (次/年)")
    print("=" * 70)
    print(f"\n{'数据窗口':<20}", end="")
    for model in ["Logistic", "Gompertz", "Richards"]:
        print(f"{model:<15}", end="")
    print()
    print("-" * 65)
    
    for window_name in ["全部数据", "近25年", "近15年", "近10年"]:
        results = all_results[window_name][0]
        print(f"{window_name:<20}", end="")
        for model in ["Logistic", "Gompertz", "Richards"]:
            if model in results and results[model]["success"]:
                print(f"{results[model]['K']:<15.0f}", end="")
            else:
                print(f"{'N/A':<15}", end="")
        print()
    
    # 物理约束分析
    physical_limits = analyze_physical_constraints(n_sites=10)
    
    # 绘制图表
    print("\n" + "=" * 70)
    print("生成图表...")
    plot_model_comparison(df, all_results)
    plot_future_predictions(all_results)
    plot_physical_vs_model(all_results, physical_limits)
    
    # 保存详细结果到CSV
    save_results_csv(all_results)
    
    print("\n" + "=" * 70)
    print("分析完成!")
    print("=" * 70)
    
    # Bootstrap 不确定性分析
    print("\n" + "=" * 70)
    print("Bootstrap 不确定性分析 (基于近15年数据, 500次重采样)")
    print("=" * 70)
    
    data_15 = windows["近15年"]
    base_year = 1957
    
    # Richards 模型 Bootstrap
    p0_ric = [2000.0, 0.08, 70.0, 2.0]
    bounds_ric = ([300, 0.01, 0, 0.1], [500000, 1.0, 300, 10.0])
    
    boot_result = bootstrap_K_estimate(data_15, richards, p0_ric, bounds_ric, n_bootstrap=500, base_year=base_year)
    if boot_result:
        print(f"\nRichards 模型 K 值 Bootstrap 估计:")
        print(f"  均值:          {boot_result['mean']:.0f} 次/年")
        print(f"  中位数:        {boot_result['median']:.0f} 次/年")
        print(f"  标准差:        {boot_result['std']:.0f}")
        print(f"  90% 置信区间:  [{boot_result['ci_5']:.0f}, {boot_result['ci_95']:.0f}] 次/年")
    
    # AIC/BIC 模型选择
    print("\n" + "=" * 70)
    print("模型选择 (AIC/BIC, 基于近15年数据)")
    print("=" * 70)
    
    results_15, years_15, launches_15, t_15, _ = all_results["近15年"]
    print(f"\n{'模型':<12} {'AIC':<12} {'BIC':<12} {'参数数':<8}")
    print("-" * 44)
    
    for model_name, res in results_15.items():
        if res["success"]:
            n_params = len(res["params"])
            aic, bic = calculate_aic_bic(launches_15, res["y_pred"], n_params)
            print(f"{model_name:<12} {aic:<12.1f} {bic:<12.1f} {n_params:<8}")
    
    # 结论
    print("\n" + "=" * 70)
    print("📊 分析结论与建议")
    print("=" * 70)
    
    # 收集有效的K值（排除撞边界的）
    valid_k_values = []
    for window_name, (results, _, _, _, _) in all_results.items():
        for model_name, res in results.items():
            if res["success"] and res["K"] < 100000:  # 排除撞边界的
                valid_k_values.append((window_name, model_name, res["K"]))
    
    print("\n1. 模型适用性分析:")
    print("-" * 50)
    print("   • Logistic/Gompertz: 数据处于快速增长期，无法确定上限")
    print("     (模型撞到边界，说明增长远未饱和)")
    print("   • Richards 曲线: 额外的形状参数使其能给出更合理估计")
    print("     (推荐用于当前阶段的上限预测)")
    
    print("\n2. 合理的上限估计 (Richards 模型):")
    print("-" * 50)
    for window_name, model_name, k in valid_k_values:
        if model_name == "Richards" and window_name != "全部数据":
            print(f"   • {window_name}: K ≈ {k:.0f} 次/年")
    
    # Richards 结果的平均值
    richards_k = [k for w, m, k in valid_k_values if m == "Richards" and w != "全部数据"]
    if richards_k:
        print(f"\n   📌 综合估计: {int(np.mean(richards_k)):,} - {int(np.max(richards_k)):,} 次/年")
    
    print("\n3. 物理约束参考 (基于10个主要发射场):")
    print("-" * 50)
    print("   • 主要发射场数量: 10个")
    print("   • 假设每发射场年最大容量:")
    print("     - 保守估计: 50次/年 → 总上限 500 次/年")
    print("     - 中等估计: 100次/年 → 总上限 1,000 次/年")
    print("     - 乐观估计: 200次/年 → 总上限 2,000 次/年")
    print("     - 极限估计 (SpaceX级别): 400次/年 → 总上限 4,000 次/年")
    print("   • 2025年实际数据: 329次 (10发射场平均 33次/场)")
    print("   • SpaceX Starship 单型号目标: 1,000+ 次/年")
    
    print("\n4. 预测不确定性:")
    print("-" * 50)
    print("   • 当前处于指数增长早期，上限估计高度不确定")
    print("   • 建议每年用新数据更新预测")
    print("   • 需关注技术突破（可重复使用、星舰等）对上限的影响")
    
    if boot_result:
        print(f"\n   Bootstrap 90%置信区间: {boot_result['ci_5']:.0f} - {boot_result['ci_95']:.0f} 次/年")


def analyze_physical_constraints(n_sites=10):
    """
    基于发射场物理约束的上限分析
    
    参数:
        n_sites: 发射场数量
    """
    print("\n" + "=" * 70)
    print(f"物理约束分析 (基于 {n_sites} 个发射场)")
    print("=" * 70)
    
    # 不同情景下的单发射场年容量
    scenarios = {
        "保守 (当前技术)": 50,
        "中等 (技术进步)": 100,
        "乐观 (快速周转)": 200,
        "极限 (SpaceX级)": 400,
        "理论极限 (每天1发)": 365,
    }
    
    print(f"\n单发射场年发射容量假设:")
    print("-" * 50)
    
    results = {}
    for scenario, capacity_per_site in scenarios.items():
        total_capacity = n_sites * capacity_per_site
        results[scenario] = total_capacity
        print(f"  {scenario:<20}: {capacity_per_site:>3} 次/场/年 → 总上限 {total_capacity:>5,} 次/年")
    
    # 带置信度的综合估计
    print(f"\n综合估计 ({n_sites} 发射场):")
    print("-" * 50)
    print(f"  50% 置信区间: {n_sites * 50:,} - {n_sites * 100:,} 次/年")
    print(f"  90% 置信区间: {n_sites * 30:,} - {n_sites * 200:,} 次/年")
    print(f"  理论最大值:   {n_sites * 365:,} 次/年 (每天每场1发)")
    
    return results


def save_results_csv(all_results, filepath="launch_capacity_results.csv"):
    """保存详细结果到CSV"""
    rows = []
    for window_name, (results, _, _, _, _) in all_results.items():
        for model_name, res in results.items():
            if res["success"]:
                row = {
                    "数据窗口": window_name,
                    "模型": model_name,
                    "饱和上限K": res["K"],
                    "拐点年份": res["inflection_year"],
                    "拐点发射数": res["inflection_value"],
                    "R²": res["r_squared"],
                    "RMSE": res["rmse"]
                }
                rows.append(row)
    
    df_results = pd.DataFrame(rows)
    df_results.to_csv(filepath, index=False, encoding="utf-8-sig")
    print(f"详细结果已保存: {filepath}")


if __name__ == "__main__":
    main()
