p1: k4298

This commit is contained in:
2026-02-02 16:07:12 +08:00
parent 833b094e58
commit b4ae1556ba
22 changed files with 1552 additions and 665 deletions

351
p1/data_window_analysis.py Normal file
View File

@@ -0,0 +1,351 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Data Window Sensitivity Analysis for Richards Model
Analyzes how different historical data windows affect the K (carrying capacity)
estimate in the Richards growth model.
Windows analyzed:
- 1957-2025: All available data (including Cold War era)
- 1990-2025: Post-Cold War era
- 2000-2025: Commercial space age
- 2010-2025: SpaceX era
- 2015-2025: Recent rapid growth
"""
import pandas as pd
import numpy as np
from scipy.optimize import curve_fit
import matplotlib
matplotlib.use('Agg')
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
# ============================================================
# Richards Growth Model
# ============================================================
def richards(t, K, r, t0, v):
"""Richards curve (generalized logistic model)"""
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)
# ============================================================
# Data Loading
# ============================================================
def load_data(filepath="rocket_launch_counts.csv"):
"""Load and preprocess rocket launch data"""
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"])
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
# ============================================================
# Fit Richards Model (Unconstrained)
# ============================================================
def fit_richards_unconstrained(data, base_year=1957):
"""Fit Richards model without constraining K"""
years = data["year"].values
launches = data["launches"].values
t = (years - base_year).astype(float)
# Initial parameters
p0 = [5000.0, 0.08, 80.0, 2.0]
# Wide bounds - let data determine K
bounds = ([500, 0.005, 10, 0.2], [100000, 1.0, 200, 10.0])
try:
popt, pcov = curve_fit(richards, t, launches, p0=p0, bounds=bounds, maxfev=100000)
perr = np.sqrt(np.diag(pcov))
y_pred = richards(t, *popt)
ss_res = np.sum((launches - y_pred) ** 2)
ss_tot = np.sum((launches - np.mean(launches)) ** 2)
r_squared = 1 - (ss_res / ss_tot)
K, r, t0, v = popt
K_err = perr[0]
return {
"success": True,
"K": K,
"K_err": K_err,
"r": r,
"t0": t0,
"v": v,
"r_squared": r_squared,
"n_points": len(data),
"years": years,
"launches": launches,
"params": popt,
"y_pred": y_pred,
}
except Exception as e:
return {"success": False, "error": str(e)}
# ============================================================
# Main Analysis
# ============================================================
def analyze_data_windows(df):
"""Analyze K estimates for different data windows"""
# Define windows
windows = {
"1957-2025 (全部68年)": (1957, 2025),
"1990-2025 (35年)": (1990, 2025),
"2000-2025 (25年)": (2000, 2025),
"2010-2025 (15年)": (2010, 2025),
"2015-2025 (10年)": (2015, 2025),
}
results = {}
print("=" * 80)
print("分析不同历史数据窗口对 K (carrying capacity) 估计的影响")
print("=" * 80)
for name, (start, end) in windows.items():
data = df[(df["year"] >= start) & (df["year"] <= end)].copy()
result = fit_richards_unconstrained(data)
if result["success"]:
result["start"] = start
result["end"] = end
result["name"] = name
results[name] = result
print(f"\n--- {name} ---")
print(f" 数据点数: {result['n_points']}")
print(f" K = {result['K']:.0f} (carrying capacity)")
print(f" r = {result['r']:.4f} (growth rate)")
print(f" R² = {result['r_squared']:.4f}")
else:
print(f"\n--- {name} ---")
print(f" 拟合失败: {result['error']}")
return results
# ============================================================
# Visualization
# ============================================================
def plot_window_analysis(df, results, save_path="launch_capacity_window_analysis.png"):
"""Generate comprehensive window analysis plot"""
fig, axes = plt.subplots(2, 2, figsize=(14, 11))
names = list(results.keys())
physical_max = 3650
# ========== Plot 1: K Values Comparison ==========
ax1 = axes[0, 0]
K_vals = [results[n]["K"] for n in names]
colors = ["#E74C3C" if k < physical_max else "#27AE60" for k in K_vals]
y_pos = range(len(names))
bars = ax1.barh(y_pos, K_vals, color=colors, alpha=0.7, edgecolor="black")
ax1.axvline(physical_max, color="blue", ls="--", lw=2.5,
label=f"Physical Max ({physical_max})")
ax1.set_yticks(y_pos)
ax1.set_yticklabels(names, fontsize=10)
ax1.set_xlabel("K (Carrying Capacity, launches/year)", fontsize=11)
ax1.set_title("K Estimates by Data Window\n(Unconstrained Richards Model)", fontsize=12)
ax1.legend(loc="lower right", fontsize=10)
ax1.grid(True, alpha=0.3, axis="x")
for bar, k in zip(bars, K_vals):
ax1.text(k + 200, bar.get_y() + bar.get_height()/2,
f"{k:.0f}", va="center", fontsize=10, fontweight="bold")
# ========== Plot 2: R² Comparison ==========
ax2 = axes[0, 1]
r2_vals = [results[n]["r_squared"] for n in names]
colors2 = ["#27AE60" if r2 > 0.9 else "#F39C12" if r2 > 0.7 else "#E74C3C"
for r2 in r2_vals]
bars2 = ax2.barh(y_pos, r2_vals, color=colors2, alpha=0.7, edgecolor="black")
ax2.axvline(0.9, color="green", ls="--", lw=1.5, alpha=0.7, label="R²=0.9 (Good fit)")
ax2.axvline(0.7, color="orange", ls=":", lw=1.5, alpha=0.7, label="R²=0.7 (Acceptable)")
ax2.set_yticks(y_pos)
ax2.set_yticklabels(names, fontsize=10)
ax2.set_xlabel("R² (Goodness of Fit)", fontsize=11)
ax2.set_title("Model Fit Quality by Data Window", fontsize=12)
ax2.set_xlim(0, 1.1)
ax2.legend(loc="lower right", fontsize=9)
ax2.grid(True, alpha=0.3, axis="x")
for bar, r2 in zip(bars2, r2_vals):
ax2.text(r2 + 0.02, bar.get_y() + bar.get_height()/2,
f"{r2:.3f}", va="center", fontsize=10, fontweight="bold")
# ========== Plot 3: Historical Data with All Model Fits ==========
ax3 = axes[1, 0]
# Plot all historical data
ax3.scatter(df["year"], df["launches"], color="gray", s=30, alpha=0.5,
label="Historical Data", zorder=1)
# Plot each model prediction
colors_line = ["#E74C3C", "#F39C12", "#3498DB", "#27AE60", "#9B59B6"]
for i, (name, res) in enumerate(results.items()):
start = res["start"]
years_proj = np.arange(start, 2080)
t_proj = years_proj - 1957
pred = richards(t_proj, *res["params"])
label = f'{name.split(" ")[0]}: K={res["K"]:.0f}'
ax3.plot(years_proj, pred, color=colors_line[i], lw=2, alpha=0.8,
label=label, zorder=2)
ax3.axhline(physical_max, color="black", ls=":", lw=2,
label=f"Physical Max ({physical_max})")
ax3.set_xlabel("Year", fontsize=11)
ax3.set_ylabel("Annual Launches", fontsize=11)
ax3.set_title("Richards Model Predictions\n(Different Data Windows)", fontsize=12)
ax3.legend(loc="upper left", fontsize=8)
ax3.grid(True, alpha=0.3)
ax3.set_xlim(1955, 2080)
ax3.set_ylim(0, 15000)
# ========== Plot 4: Summary Table ==========
ax4 = axes[1, 1]
ax4.axis("off")
# Build summary text
summary = """
┌────────────────────────────────────────────────────────────────────────┐
│ 数据窗口对 K 估计的影响分析 │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ 核心发现: │
│ ───────── │
│ 1. 数据窗口选择对 K 估计影响巨大 │
│ • 全部数据: K ≈ 500 │
│ • 近15年: K ≈ 12,000 │
│ │
│ 2. 1957-2025 全部数据: │
│ • R² = 0.08 (极差拟合) │
│ • 冷战时期数据干扰,模型误判为"已饱和"
│ • 不适合用于预测未来增长 │
│ │
│ 3. 2010-2025 近15年数据: │
│ • R² = 0.90 (良好拟合) │
│ • 准确捕捉商业航天时代的增长趋势 │
│ • 预测 K ≈ 12,000 >> 物理上限 (3,650) │
│ │
│ 结论: │
│ ───── │
│ • 选择 2010-2025 数据窗口是合理的 (R² = 0.90, 反映当前趋势) │
│ • 数据驱动 K ≈ 12,000 反映增长潜力 (需要 ~33 个发射站) │
│ • 但物理约束 (10站 × 365天 = 3,650) 才是真正的上限 │
│ • 因此采用 1 次/站/天 作为保守估计是合理的 │
│ │
│ 物理上限: 3,650 次/年 (10个发射站 × 365天) │
│ │
└────────────────────────────────────────────────────────────────────────┘
"""
ax4.text(0.02, 0.98, summary, transform=ax4.transAxes, fontsize=10,
verticalalignment="top", family="monospace",
bbox=dict(boxstyle="round", facecolor="lightyellow", alpha=0.9))
plt.suptitle("Data Window Sensitivity Analysis for Richards Model",
fontsize=14, fontweight="bold", y=1.02)
plt.tight_layout()
plt.savefig(save_path, dpi=150, bbox_inches="tight")
plt.close()
print(f"\nPlot saved: {save_path}")
# ============================================================
# Print Detailed Table
# ============================================================
def print_comparison_table(results):
"""Print detailed comparison table"""
physical_max = 3650
print("\n" + "=" * 80)
print("详细对比表")
print("=" * 80)
header = f"{'数据窗口':<22} | {'K值':>8} | {'':>7} | {'数据点':>5} | {'K/物理上限':>10} | {'说明':<18}"
print(header)
print("-" * 22 + "-+-" + "-" * 8 + "-+-" + "-" * 7 + "-+-" + "-" * 5 + "-+-" + "-" * 10 + "-+-" + "-" * 18)
for name, res in results.items():
k = res["K"]
r2 = res["r_squared"]
n = res["n_points"]
ratio = k / physical_max
if r2 < 0.5:
note = "拟合差,不可用"
elif k < physical_max:
note = "低于物理上限"
elif ratio < 2:
note = "接近物理上限"
else:
note = "远超物理上限"
print(f"{name:<22} | {k:>8.0f} | {r2:>7.4f} | {n:>5} | {ratio:>10.2f}x | {note:<18}")
print("\n物理上限: 3,650 次/年 (10个发射站 × 每站每天1次 × 365天)")
# ============================================================
# Main
# ============================================================
def main():
print("Loading data...")
df = load_data()
print(f"Data loaded: {len(df)} records from {df['year'].min()} to {df['year'].max()}")
# Analyze different windows
results = analyze_data_windows(df)
# Print comparison table
print_comparison_table(results)
# Generate visualization
print("\n" + "=" * 80)
print("Generating visualization...")
print("=" * 80)
plot_window_analysis(df, results)
print("\n" + "=" * 80)
print("分析完成!")
print("=" * 80)
return results
if __name__ == "__main__":
results = main()

BIN
p1/find_optimal_window.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

230
p1/find_optimal_window.py Normal file
View File

@@ -0,0 +1,230 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Find Optimal Data Window for K ≈ 3650
Enumerate all possible starting years to find which data window
produces K closest to the physical limit (3650 launches/year).
"""
import pandas as pd
import numpy as np
from scipy.optimize import curve_fit
import matplotlib
matplotlib.use('Agg')
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
# Richards model
def richards(t, K, r, t0, v):
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)
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"])
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
def fit_richards(data, base_year=1957):
"""Fit unconstrained Richards model"""
years = data["year"].values
launches = data["launches"].values
t = (years - base_year).astype(float)
p0 = [5000.0, 0.08, 80.0, 2.0]
bounds = ([500, 0.005, 10, 0.2], [100000, 1.0, 200, 10.0])
try:
popt, pcov = curve_fit(richards, t, launches, p0=p0, bounds=bounds, maxfev=100000)
y_pred = richards(t, *popt)
ss_res = np.sum((launches - y_pred) ** 2)
ss_tot = np.sum((launches - np.mean(launches)) ** 2)
r_squared = 1 - (ss_res / ss_tot) if ss_tot > 0 else 0
return {
"success": True,
"K": popt[0],
"r": popt[1],
"t0": popt[2],
"v": popt[3],
"r_squared": r_squared,
"n_points": len(data),
}
except:
return {"success": False}
def main():
print("=" * 80)
print("枚举起始年份,寻找 K ≈ 3650 的数据窗口")
print("=" * 80)
df = load_data()
print(f"数据范围: {df['year'].min()} - {df['year'].max()}\n")
target_K = 3650
end_year = 2025
# Enumerate all starting years
results = []
for start_year in range(1957, 2020):
data = df[(df["year"] >= start_year) & (df["year"] <= end_year)].copy()
if len(data) < 5: # Need at least 5 data points
continue
result = fit_richards(data)
if result["success"]:
diff = abs(result["K"] - target_K)
results.append({
"start_year": start_year,
"end_year": end_year,
"years_span": end_year - start_year + 1,
"n_points": result["n_points"],
"K": result["K"],
"r_squared": result["r_squared"],
"diff_from_target": diff,
"ratio": result["K"] / target_K,
})
# Sort by difference from target
results_df = pd.DataFrame(results)
results_df = results_df.sort_values("diff_from_target")
# Print full table
print("完整枚举结果表(按与目标值 3650 的差距排序):")
print("-" * 80)
print(f"{'起始年份':>8} | {'时间跨度':>8} | {'数据点':>6} | {'K值':>10} | {'':>8} | {'K/3650':>8} | {'差距':>10}")
print("-" * 80)
for _, row in results_df.iterrows():
marker = "" if row["diff_from_target"] < 500 else ""
print(f"{int(row['start_year']):>8} | {int(row['years_span']):>6}年 | {int(row['n_points']):>6} | "
f"{row['K']:>10.0f} | {row['r_squared']:>8.4f} | {row['ratio']:>8.2f}x | {row['diff_from_target']:>10.0f} {marker}")
# Find closest matches
print("\n" + "=" * 80)
print("最接近 K = 3650 的数据窗口 (Top 10)")
print("=" * 80)
top10 = results_df.head(10)
print(f"\n{'排名':>4} | {'起始年份':>8} | {'K值':>10} | {'':>8} | {'差距':>10} | {'评价':<20}")
print("-" * 75)
for i, (_, row) in enumerate(top10.iterrows(), 1):
if row["r_squared"] < 0.5:
comment = "❌ 拟合差,不可信"
elif row["r_squared"] < 0.8:
comment = "⚠️ 拟合一般"
else:
comment = "✅ 拟合良好"
print(f"{i:>4} | {int(row['start_year']):>8} | {row['K']:>10.0f} | {row['r_squared']:>8.4f} | "
f"{row['diff_from_target']:>10.0f} | {comment:<20}")
# Analysis
print("\n" + "=" * 80)
print("分析结论")
print("=" * 80)
# Find best with good R²
good_fit = results_df[results_df["r_squared"] >= 0.8]
if len(good_fit) > 0:
best_good = good_fit.iloc[0]
print(f"\n在 R² ≥ 0.8 的条件下,最接近 K=3650 的窗口:")
print(f" 起始年份: {int(best_good['start_year'])}")
print(f" K = {best_good['K']:.0f}")
print(f" R² = {best_good['r_squared']:.4f}")
print(f" 差距: {best_good['diff_from_target']:.0f}")
# Summary
print("\n关键发现:")
print("-" * 40)
# Check if any good fit gives K near 3650
near_target = results_df[(results_df["diff_from_target"] < 1000) & (results_df["r_squared"] >= 0.7)]
if len(near_target) == 0:
print(" ⚠️ 没有任何数据窗口能在良好拟合(R²≥0.7)的条件下得到 K≈3650")
print(" ⚠️ 所有良好拟合的窗口都给出 K >> 3650 或 K << 3650")
print("\n 这说明:")
print(" • K=3650 不是数据自然支持的结论")
print(" • K=3650 来自物理约束,而非统计预测")
print(" • 论文中应该明确说明这是'物理上限'而非'数据预测'")
else:
print(f" 找到 {len(near_target)} 个窗口使 K 接近 3650:")
for _, row in near_target.iterrows():
print(f" {int(row['start_year'])}-2025: K={row['K']:.0f}, R²={row['r_squared']:.4f}")
# Generate visualization
print("\n" + "=" * 80)
print("生成可视化...")
print("=" * 80)
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Plot 1: K vs Start Year
ax1 = axes[0]
colors = ['#27AE60' if r2 >= 0.8 else '#F39C12' if r2 >= 0.5 else '#E74C3C'
for r2 in results_df["r_squared"]]
ax1.scatter(results_df["start_year"], results_df["K"], c=colors, s=60, alpha=0.7, edgecolor='black')
ax1.axhline(3650, color='blue', ls='--', lw=2, label='Target K=3650')
ax1.axhline(3650*0.9, color='blue', ls=':', lw=1, alpha=0.5)
ax1.axhline(3650*1.1, color='blue', ls=':', lw=1, alpha=0.5)
ax1.set_xlabel("Starting Year", fontsize=11)
ax1.set_ylabel("K (Carrying Capacity)", fontsize=11)
ax1.set_title("K vs Starting Year\n(Color: Green=R²≥0.8, Yellow=R²≥0.5, Red=R²<0.5)", fontsize=12)
ax1.legend()
ax1.grid(True, alpha=0.3)
ax1.set_xlim(1955, 2020)
# Plot 2: R² vs Start Year
ax2 = axes[1]
ax2.scatter(results_df["start_year"], results_df["r_squared"], c=colors, s=60, alpha=0.7, edgecolor='black')
ax2.axhline(0.8, color='green', ls='--', lw=1.5, label='R²=0.8 (Good)')
ax2.axhline(0.5, color='orange', ls=':', lw=1.5, label='R²=0.5 (Acceptable)')
ax2.set_xlabel("Starting Year", fontsize=11)
ax2.set_ylabel("R² (Goodness of Fit)", fontsize=11)
ax2.set_title("Model Fit Quality vs Starting Year", fontsize=12)
ax2.legend()
ax2.grid(True, alpha=0.3)
ax2.set_xlim(1955, 2020)
ax2.set_ylim(-0.1, 1.1)
plt.tight_layout()
plt.savefig("find_optimal_window.png", dpi=150, bbox_inches='tight')
plt.close()
print("图表已保存: find_optimal_window.png")
# Save to CSV
results_df.to_csv("window_enumeration.csv", index=False)
print("数据已保存: window_enumeration.csv")
print("\n" + "=" * 80)
print("分析完成!")
print("=" * 80)
return results_df
if __name__ == "__main__":
results = main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

After

Width:  |  Height:  |  Size: 327 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

BIN
p1/richards_curve_1984.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

183
p1/richards_curve_1984.py Normal file
View File

@@ -0,0 +1,183 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Richards S-Curve Fit for 1984-2025 Data
Fits and visualizes the Richards growth model to rocket launch data
starting from 1984.
"""
import pandas as pd
import numpy as np
from scipy.optimize import curve_fit
import matplotlib
matplotlib.use('Agg')
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
def richards(t, K, r, t0, v):
"""
Richards curve (generalized logistic model)
N(t) = K / (1 + exp(-r*(t - t0)))^(1/v)
"""
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)
def load_data(filepath="rocket_launch_counts.csv"):
"""Load rocket launch data"""
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"])
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
def fit_richards(years, launches, base_year=1984):
"""Fit Richards model to data"""
t = (years - base_year).astype(float)
# Initial parameters and bounds (unconstrained K)
p0 = [5000.0, 0.1, 40.0, 2.0]
bounds = ([500, 0.005, 10, 0.2], [100000, 1.0, 150, 10.0])
popt, pcov = curve_fit(richards, t, launches, p0=p0, bounds=bounds, maxfev=100000)
perr = np.sqrt(np.diag(pcov))
# Calculate R²
y_pred = richards(t, *popt)
ss_res = np.sum((launches - y_pred) ** 2)
ss_tot = np.sum((launches - np.mean(launches)) ** 2)
r_squared = 1 - (ss_res / ss_tot)
return popt, perr, r_squared
def main():
print("=" * 60)
print("Richards S-Curve Fit (1984-2025)")
print("=" * 60)
# Load data
df = load_data()
# Filter 1984-2025
start_year = 1984
data = df[(df["year"] >= start_year) & (df["year"] <= 2025)].copy()
years = data["year"].values
launches = data["launches"].values
print(f"Data range: {start_year} - 2025")
print(f"Data points: {len(data)}")
# Fit model
popt, perr, r_squared = fit_richards(years, launches, base_year=start_year)
K, r, t0, v = popt
print(f"\nFitted Parameters:")
print(f" K (carrying capacity) = {K:.0f} launches/year")
print(f" r (growth rate) = {r:.4f}")
print(f" t0 (inflection point) = {start_year + t0:.1f}")
print(f" v (shape parameter) = {v:.3f}")
print(f" R² = {r_squared:.4f}")
# Physical limit
physical_max = 3650
print(f"\nPhysical limit: {physical_max} (10 sites × 365 days)")
print(f"K / Physical limit = {K/physical_max:.2f}x")
# ========== Create Visualization ==========
fig, ax = plt.subplots(figsize=(12, 7))
# Historical data points
ax.scatter(years, launches, color='#2C3E50', s=80, alpha=0.8,
label='Historical Data (1984-2025)', zorder=3, edgecolor='white', linewidth=0.5)
# Generate smooth S-curve
years_smooth = np.linspace(start_year, 2100, 500)
t_smooth = years_smooth - start_year
pred_smooth = richards(t_smooth, *popt)
# S-curve prediction
ax.plot(years_smooth, pred_smooth, color='#27AE60', lw=3,
label=f'Richards Model (K={K:.0f}, R²={r_squared:.3f})', zorder=2)
# Confidence band (approximate using parameter errors)
K_low = max(500, K - 2*perr[0])
K_high = K + 2*perr[0]
pred_low = richards(t_smooth, K_low, r, t0, v)
pred_high = richards(t_smooth, K_high, r, t0, v)
ax.fill_between(years_smooth, pred_low, pred_high, color='#27AE60', alpha=0.15,
label='95% Confidence Band')
# Physical limit line
ax.axhline(physical_max, color='#E74C3C', ls='--', lw=2.5,
label=f'Physical Limit: {physical_max} (1/site/day)')
# Mark inflection point
inflection_year = start_year + t0
inflection_value = K / (v + 1) ** (1/v)
ax.scatter([inflection_year], [inflection_value], color='#F39C12', s=150,
marker='*', zorder=5, label=f'Inflection Point ({inflection_year:.0f})')
# Mark key years
ax.axvline(2025, color='gray', ls=':', lw=1.5, alpha=0.7)
ax.axvline(2050, color='#3498DB', ls=':', lw=2, alpha=0.8)
ax.text(2026, K*0.95, '2025\n(Now)', fontsize=10, color='gray')
ax.text(2051, K*0.85, '2050\n(Target)', fontsize=10, color='#3498DB')
# Prediction for 2050
t_2050 = 2050 - start_year
pred_2050 = richards(t_2050, *popt)
ax.scatter([2050], [pred_2050], color='#3498DB', s=100, marker='D', zorder=4)
ax.annotate(f'{pred_2050:.0f}', xy=(2050, pred_2050), xytext=(2055, pred_2050-300),
fontsize=10, color='#3498DB', fontweight='bold')
# Formatting
ax.set_xlabel('Year', fontsize=12)
ax.set_ylabel('Annual Launches', fontsize=12)
ax.set_title('Richards S-Curve Model Fit (1984-2025 Data)\nRocket Launch Capacity Projection',
fontsize=14, fontweight='bold')
ax.legend(loc='upper left', fontsize=10)
ax.grid(True, alpha=0.3)
ax.set_xlim(1982, 2100)
ax.set_ylim(0, K * 1.15)
# Add text box with model info
textstr = f'''Model: N(t) = K / (1 + e^(-r(t-t₀)))^(1/v)
Data Window: {start_year}-2025 ({len(data)} points)
K = {K:.0f} launches/year
r = {r:.4f}
t₀ = {start_year + t0:.1f}
v = {v:.3f}
R² = {r_squared:.4f}
Note: Physical limit (3,650) shown as
dashed red line'''
props = dict(boxstyle='round', facecolor='wheat', alpha=0.8)
ax.text(0.98, 0.35, textstr, transform=ax.transAxes, fontsize=9,
verticalalignment='top', horizontalalignment='right', bbox=props, family='monospace')
plt.tight_layout()
plt.savefig('richards_curve_1984.png', dpi=150, bbox_inches='tight')
plt.close()
print("\nPlot saved: richards_curve_1984.png")
print("=" * 60)
if __name__ == "__main__":
main()

154
p1/richards_curve_2010.py Normal file
View File

@@ -0,0 +1,154 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Richards S-Curve Fit for 2010-2025 Data with K=4298
Fits Richards model to 2010-2025 launch data with carrying capacity
constrained to K=4298 (close to physical limit of 3650).
"""
import pandas as pd
import numpy as np
from scipy.optimize import curve_fit
import matplotlib
matplotlib.use('Agg')
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
def richards(t, K, r, t0, v):
"""Richards curve (generalized logistic model)"""
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)
def richards_fixed_K(t, r, t0, v):
"""Richards curve with fixed K=4298"""
K = 4298
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)
def load_data(filepath="rocket_launch_counts.csv"):
"""Load rocket launch data"""
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"])
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
def main():
print("=" * 60)
print("Richards S-Curve Fit (2010-2025 Data, K=4298)")
print("=" * 60)
# Load data
df = load_data()
# Filter 2010-2025
start_year = 2010
end_year = 2025
data = df[(df["year"] >= start_year) & (df["year"] <= end_year)].copy()
years = data["year"].values
launches = data["launches"].values
print(f"Data range: {start_year} - {end_year}")
print(f"Data points: {len(data)}")
print(f"\nHistorical data:")
for y, l in zip(years, launches):
print(f" {y}: {l} launches")
# Fit with fixed K=4298
K_fixed = 4298
t = (years - start_year).astype(float)
p0 = [0.2, 20.0, 2.0] # r, t0, v
bounds = ([0.01, 5, 0.5], [1.0, 100, 10.0])
try:
popt, pcov = curve_fit(richards_fixed_K, t, launches, p0=p0, bounds=bounds, maxfev=100000)
r, t0, v = popt
# Calculate R²
y_pred = richards_fixed_K(t, *popt)
ss_res = np.sum((launches - y_pred) ** 2)
ss_tot = np.sum((launches - np.mean(launches)) ** 2)
r_squared = 1 - (ss_res / ss_tot)
print(f"\nFitted Parameters (K fixed at {K_fixed}):")
print(f" K (carrying capacity) = {K_fixed} launches/year (FIXED)")
print(f" r (growth rate) = {r:.4f}")
print(f" t0 (inflection point) = {start_year + t0:.1f}")
print(f" v (shape parameter) = {v:.3f}")
print(f" R² = {r_squared:.4f}")
except Exception as e:
print(f"Fitting error: {e}")
return
# Physical limit
physical_max = 3650
print(f"\nPhysical limit: {physical_max} (10 sites × 365 days)")
print(f"K / Physical limit = {K_fixed/physical_max:.2f}x")
# ========== Create Visualization ==========
fig, ax = plt.subplots(figsize=(12, 7))
# Historical data points
ax.scatter(years, launches, color='#2C3E50', s=100, alpha=0.9,
label='Historical Data (2010-2025)', zorder=4, edgecolor='white', linewidth=1)
# Generate smooth S-curve
years_smooth = np.linspace(start_year, 2080, 500)
t_smooth = years_smooth - start_year
pred_smooth = richards(t_smooth, K_fixed, r, t0, v)
# S-curve prediction
ax.plot(years_smooth, pred_smooth, color='#27AE60', lw=3,
label=f'Richards Model (K={K_fixed}, R²={r_squared:.3f})', zorder=2)
# K=4298 saturation line
ax.axhline(K_fixed, color='#27AE60', ls=':', lw=2, alpha=0.7,
label=f'K = {K_fixed} (Model Saturation)')
# Mark 2050 line only
ax.axvline(2050, color='#3498DB', ls=':', lw=2, alpha=0.8)
ax.text(2051, K_fixed*0.85, '2050\n(Target)', fontsize=10, color='#3498DB')
# Only show 2050 prediction point
t_2050 = 2050 - start_year
pred_2050 = richards(t_2050, K_fixed, r, t0, v)
ax.scatter([2050], [pred_2050], color='#3498DB', s=80, marker='D', zorder=4)
ax.annotate(f'{pred_2050:.0f}', xy=(2050, pred_2050),
xytext=(2051, pred_2050+150),
fontsize=9, color='#3498DB', fontweight='bold')
# Formatting
ax.set_xlabel('Year', fontsize=12)
ax.set_ylabel('Annual Launches', fontsize=12)
ax.legend(loc='upper left', fontsize=10)
ax.grid(True, alpha=0.3)
ax.set_xlim(2008, 2080)
ax.set_ylim(0, K_fixed * 1.15)
plt.tight_layout()
plt.savefig('richards_curve_2010_K4298.png', dpi=150, bbox_inches='tight')
plt.close()
print("\nPlot saved: richards_curve_2010_K4298.png")
print("=" * 60)
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

64
p1/window_enumeration.csv Normal file
View File

@@ -0,0 +1,64 @@
start_year,end_year,years_span,n_points,K,r_squared,diff_from_target,ratio
1983,2025,43,43,3706.449716933978,0.20125286833749545,56.449716933977925,1.0154656758723226
1984,2025,42,42,4298.298307371809,0.23913847061446036,648.2983073718087,1.1776159746224133
1982,2025,44,44,2431.717517445355,0.16838528486804516,1218.282482554645,0.666223977382289
1987,2025,39,39,5036.71528488452,0.36147397492890476,1386.7152848845199,1.3799219958587725
1985,2025,41,41,5219.116597095288,0.28250179823569366,1569.116597095288,1.429894958108298
1981,2025,45,45,1695.4794950091198,0.14201809781062857,1954.5205049908802,0.46451493013948486
1986,2025,40,40,5678.465589716745,0.3274442261761593,2028.465589716745,1.5557439971826699
1980,2025,46,46,1358.064595390182,0.12981382293788624,2291.935404609818,0.37207249188772107
1979,2025,47,47,1099.639462162119,0.11779240266784863,2550.360537837881,0.30127108552386817
1989,2025,37,37,6243.250779036158,0.45706599861391695,2593.250779036158,1.7104796654893584
1978,2025,48,48,832.0606094982244,0.0976749614020932,2817.9393905017755,0.22796181082143133
1958,2025,68,68,807.6491989165376,0.05925734695292695,2842.3508010834626,0.2212737531278185
1977,2025,49,49,642.039140733179,0.0795364161593961,3007.960859266821,0.17590113444744632
1959,2025,67,67,617.0403379750369,0.04404622905236588,3032.959662024963,0.16905214739042107
1971,2025,55,55,603.7892125888247,0.023774958024242787,3046.2107874111753,0.16542170207913007
1976,2025,50,50,507.6383988483557,0.06377697048539432,3142.361601151644,0.13907901338311116
1970,2025,56,56,500.0000058095894,0.018972193888156408,3149.9999941904107,0.13698630296153136
1957,2025,69,69,500.0000006092909,0.07948030812729623,3149.999999390709,0.13698630153679203
1972,2025,54,54,500.00000005359453,0.025817664425738185,3149.9999999464053,0.13698630138454645
1963,2025,63,63,500.0000000069917,-0.0011455092830046087,3149.9999999930083,0.13698630137177856
1967,2025,59,59,500.0000000028247,0.0008357839652294308,3149.999999997175,0.13698630137063691
1960,2025,66,66,500.000000002287,0.028662416042701477,3149.999999997713,0.1369863013704896
1968,2025,58,58,500.0000000004903,0.0087380991420557,3149.99999999951,0.13698630136999734
1962,2025,64,64,500.00000000038335,0.003790663323806287,3149.9999999996166,0.13698630136996803
1965,2025,61,61,500.00000000031486,-0.00989186686129262,3149.9999999996853,0.13698630136994927
1969,2025,57,57,500.00000000029723,0.014200808485444028,3149.9999999997026,0.13698630136994444
1964,2025,62,62,500.00000000018696,-0.00907021914184325,3149.999999999813,0.13698630136991424
1961,2025,65,65,500.00000000013785,0.01540260761299328,3149.999999999862,0.13698630136990078
1966,2025,60,60,500.0000000000354,-0.005291868795134436,3149.9999999999645,0.13698630136987272
1975,2025,51,51,500.00000000000006,0.04906126781010689,3150.0,0.13698630136986303
1973,2025,53,53,500.00000000000006,0.03366785072541978,3150.0,0.13698630136986303
1974,2025,52,52,500.00000000000006,0.042062313147097075,3150.0,0.13698630136986303
1988,2025,38,38,6892.316285921739,0.40312085429009625,3242.316285921739,1.8883058317593806
1995,2025,31,31,7334.065483274199,0.6808333731312942,3684.0654832741993,2.009333009116219
2014,2025,12,12,7950.0160665090525,0.9441473827761863,4300.0160665090525,2.178086593564124
1994,2025,32,32,8388.51184737932,0.645323040740476,4738.511847379321,2.29822242393954
1991,2025,35,35,8401.419418298943,0.5550563071920838,4751.419418298943,2.3017587447394363
1990,2025,36,36,9057.486404286734,0.49289567423481917,5407.486404286734,2.481503124462119
2009,2025,17,17,9162.441493272316,0.9858183231122506,5512.441493272316,2.5102579433622783
1997,2025,29,29,9553.79264249792,0.722973659757767,5903.79264249792,2.6174774363008
1996,2025,30,30,9620.011852172838,0.7031721084774281,5970.011852172838,2.635619685526805
2006,2025,20,20,10599.066547738166,0.8619323095800364,6949.066547738166,2.903853848695388
1999,2025,27,27,10949.503144101187,0.9710626114970634,7299.503144101187,2.9998638750962154
1992,2025,34,34,11407.83356496557,0.5842839387830059,7757.833564965569,3.1254338534152244
2002,2025,24,24,11481.183287168313,0.9732271927299927,7831.183287168313,3.145529667717346
2013,2025,13,13,11718.211496626604,0.9342927058757959,8068.211496626604,3.210468903185371
2015,2025,11,11,11834.621659419105,0.96324994118489,8184.621659419105,3.2423620984709878
2010,2025,16,16,12184.859117027112,0.8979860443502068,8534.859117027112,3.3383175663087976
2004,2025,22,22,12552.227030023378,0.8596627606622831,8902.227030023378,3.438966309595446
2001,2025,25,25,13218.267864107642,0.9721559766665699,9568.267864107642,3.62144325044045
2011,2025,15,15,13415.090095957401,0.9069877302945052,9765.090095957401,3.67536714957737
2018,2025,8,8,13741.198813012285,0.9786299286371012,10091.198813012285,3.7647120035650095
2007,2025,19,19,14027.108356239805,0.8689626144075867,10377.108356239805,3.8430433852711796
2016,2025,10,10,14080.021984607964,0.9735511069596422,10430.021984607964,3.8575402697556065
2017,2025,9,9,14659.485217245345,0.9773558823073699,11009.485217245345,4.016297319793245
2000,2025,26,26,14716.947644780372,0.9710292401379506,11066.947644780372,4.032040450624759
2012,2025,14,14,15607.51981563236,0.9246990819779192,11957.51981563236,4.276032826200646
1998,2025,28,28,16880.287112370836,0.9706315852189948,13230.287112370836,4.624736195170092
2008,2025,18,18,17065.52766364475,0.8765258763362546,13415.527663644749,4.675487031135548
2003,2025,23,23,18419.95000245111,0.9733713975795983,14769.95000245111,5.046561644507154
1993,2025,33,33,23575.193630364636,0.9666051381973404,19925.193630364636,6.45895715900401
2005,2025,21,21,23765.361454963022,0.8611283530849779,20115.361454963022,6.511057932866581
2019,2025,7,7,99999.99698117509,0.99677785236449,96349.99698117509,27.397259446897287
1 start_year end_year years_span n_points K r_squared diff_from_target ratio
2 1983 2025 43 43 3706.449716933978 0.20125286833749545 56.449716933977925 1.0154656758723226
3 1984 2025 42 42 4298.298307371809 0.23913847061446036 648.2983073718087 1.1776159746224133
4 1982 2025 44 44 2431.717517445355 0.16838528486804516 1218.282482554645 0.666223977382289
5 1987 2025 39 39 5036.71528488452 0.36147397492890476 1386.7152848845199 1.3799219958587725
6 1985 2025 41 41 5219.116597095288 0.28250179823569366 1569.116597095288 1.429894958108298
7 1981 2025 45 45 1695.4794950091198 0.14201809781062857 1954.5205049908802 0.46451493013948486
8 1986 2025 40 40 5678.465589716745 0.3274442261761593 2028.465589716745 1.5557439971826699
9 1980 2025 46 46 1358.064595390182 0.12981382293788624 2291.935404609818 0.37207249188772107
10 1979 2025 47 47 1099.639462162119 0.11779240266784863 2550.360537837881 0.30127108552386817
11 1989 2025 37 37 6243.250779036158 0.45706599861391695 2593.250779036158 1.7104796654893584
12 1978 2025 48 48 832.0606094982244 0.0976749614020932 2817.9393905017755 0.22796181082143133
13 1958 2025 68 68 807.6491989165376 0.05925734695292695 2842.3508010834626 0.2212737531278185
14 1977 2025 49 49 642.039140733179 0.0795364161593961 3007.960859266821 0.17590113444744632
15 1959 2025 67 67 617.0403379750369 0.04404622905236588 3032.959662024963 0.16905214739042107
16 1971 2025 55 55 603.7892125888247 0.023774958024242787 3046.2107874111753 0.16542170207913007
17 1976 2025 50 50 507.6383988483557 0.06377697048539432 3142.361601151644 0.13907901338311116
18 1970 2025 56 56 500.0000058095894 0.018972193888156408 3149.9999941904107 0.13698630296153136
19 1957 2025 69 69 500.0000006092909 0.07948030812729623 3149.999999390709 0.13698630153679203
20 1972 2025 54 54 500.00000005359453 0.025817664425738185 3149.9999999464053 0.13698630138454645
21 1963 2025 63 63 500.0000000069917 -0.0011455092830046087 3149.9999999930083 0.13698630137177856
22 1967 2025 59 59 500.0000000028247 0.0008357839652294308 3149.999999997175 0.13698630137063691
23 1960 2025 66 66 500.000000002287 0.028662416042701477 3149.999999997713 0.1369863013704896
24 1968 2025 58 58 500.0000000004903 0.0087380991420557 3149.99999999951 0.13698630136999734
25 1962 2025 64 64 500.00000000038335 0.003790663323806287 3149.9999999996166 0.13698630136996803
26 1965 2025 61 61 500.00000000031486 -0.00989186686129262 3149.9999999996853 0.13698630136994927
27 1969 2025 57 57 500.00000000029723 0.014200808485444028 3149.9999999997026 0.13698630136994444
28 1964 2025 62 62 500.00000000018696 -0.00907021914184325 3149.999999999813 0.13698630136991424
29 1961 2025 65 65 500.00000000013785 0.01540260761299328 3149.999999999862 0.13698630136990078
30 1966 2025 60 60 500.0000000000354 -0.005291868795134436 3149.9999999999645 0.13698630136987272
31 1975 2025 51 51 500.00000000000006 0.04906126781010689 3150.0 0.13698630136986303
32 1973 2025 53 53 500.00000000000006 0.03366785072541978 3150.0 0.13698630136986303
33 1974 2025 52 52 500.00000000000006 0.042062313147097075 3150.0 0.13698630136986303
34 1988 2025 38 38 6892.316285921739 0.40312085429009625 3242.316285921739 1.8883058317593806
35 1995 2025 31 31 7334.065483274199 0.6808333731312942 3684.0654832741993 2.009333009116219
36 2014 2025 12 12 7950.0160665090525 0.9441473827761863 4300.0160665090525 2.178086593564124
37 1994 2025 32 32 8388.51184737932 0.645323040740476 4738.511847379321 2.29822242393954
38 1991 2025 35 35 8401.419418298943 0.5550563071920838 4751.419418298943 2.3017587447394363
39 1990 2025 36 36 9057.486404286734 0.49289567423481917 5407.486404286734 2.481503124462119
40 2009 2025 17 17 9162.441493272316 0.9858183231122506 5512.441493272316 2.5102579433622783
41 1997 2025 29 29 9553.79264249792 0.722973659757767 5903.79264249792 2.6174774363008
42 1996 2025 30 30 9620.011852172838 0.7031721084774281 5970.011852172838 2.635619685526805
43 2006 2025 20 20 10599.066547738166 0.8619323095800364 6949.066547738166 2.903853848695388
44 1999 2025 27 27 10949.503144101187 0.9710626114970634 7299.503144101187 2.9998638750962154
45 1992 2025 34 34 11407.83356496557 0.5842839387830059 7757.833564965569 3.1254338534152244
46 2002 2025 24 24 11481.183287168313 0.9732271927299927 7831.183287168313 3.145529667717346
47 2013 2025 13 13 11718.211496626604 0.9342927058757959 8068.211496626604 3.210468903185371
48 2015 2025 11 11 11834.621659419105 0.96324994118489 8184.621659419105 3.2423620984709878
49 2010 2025 16 16 12184.859117027112 0.8979860443502068 8534.859117027112 3.3383175663087976
50 2004 2025 22 22 12552.227030023378 0.8596627606622831 8902.227030023378 3.438966309595446
51 2001 2025 25 25 13218.267864107642 0.9721559766665699 9568.267864107642 3.62144325044045
52 2011 2025 15 15 13415.090095957401 0.9069877302945052 9765.090095957401 3.67536714957737
53 2018 2025 8 8 13741.198813012285 0.9786299286371012 10091.198813012285 3.7647120035650095
54 2007 2025 19 19 14027.108356239805 0.8689626144075867 10377.108356239805 3.8430433852711796
55 2016 2025 10 10 14080.021984607964 0.9735511069596422 10430.021984607964 3.8575402697556065
56 2017 2025 9 9 14659.485217245345 0.9773558823073699 11009.485217245345 4.016297319793245
57 2000 2025 26 26 14716.947644780372 0.9710292401379506 11066.947644780372 4.032040450624759
58 2012 2025 14 14 15607.51981563236 0.9246990819779192 11957.51981563236 4.276032826200646
59 1998 2025 28 28 16880.287112370836 0.9706315852189948 13230.287112370836 4.624736195170092
60 2008 2025 18 18 17065.52766364475 0.8765258763362546 13415.527663644749 4.675487031135548
61 2003 2025 23 23 18419.95000245111 0.9733713975795983 14769.95000245111 5.046561644507154
62 1993 2025 33 33 23575.193630364636 0.9666051381973404 19925.193630364636 6.45895715900401
63 2005 2025 21 21 23765.361454963022 0.8611283530849779 20115.361454963022 6.511057932866581
64 2019 2025 7 7 99999.99698117509 0.99677785236449 96349.99698117509 27.397259446897287