diff --git a/combined_decision.png b/combined_decision.png
new file mode 100644
index 0000000..67197a1
Binary files /dev/null and b/combined_decision.png differ
diff --git a/decision_recommendation.png b/decision_recommendation.png
new file mode 100644
index 0000000..0dd6828
Binary files /dev/null and b/decision_recommendation.png differ
diff --git a/p1/README.md b/p1/README.md
index dc7c631..52ccccf 100644
--- a/p1/README.md
+++ b/p1/README.md
@@ -23,6 +23,8 @@
纯火箭方案(10 发射场 * 每日 1 发)排布与能量-工期曲线;并做发射频率敏感性分析。
- **`combined_scenario.py`**
混合方案(电梯优先 + 火箭补齐),输出组合方案能量-工期曲线,并生成 **100–300 年**区间三方案同图对比。
+- **`../pareto_optimization.py`**(新增)
+ **Pareto 前沿 + “膝点(knee point)”**分析:在**组合方案可行区间(约 101–186 年)**内,寻找“时间–能量”双目标权衡的拐点,并输出推荐决策点与图表/CSV。
- **`launch_capacity_analysis.py`**
(可选)对历史火箭年发射量做 Logistic / Gompertz / Richards 拟合与预测,生成 `launch_capacity_*.png` 与 `launch_capacity_results.csv`。
@@ -38,6 +40,8 @@
| `combined_scenario_analysis.png` | 混合方案:总能量/电梯能量/火箭能量随工期变化 + 电梯占比 + 需要启用的火箭站点数 |
| `scenario_comparison.png` | elevator-only / rocket-only / combined 的完工时间与能量柱状对比 |
| **`three_scenarios_comparison.png`** | **100–300 年区间**:纯火箭、纯电梯、混合方案的“年份-能量”折线同图对比 |
+| **`../pareto_combined_range.png`** | **组合方案(101–186 年)Pareto 前沿 + 膝点检测**(多方法对比、边际收益、构成变化) |
+| **`../combined_decision.png`** | 组合方案(101–186 年)推荐拐点的“决策图”(更适合直接放报告) |
| `launch_capacity_analysis.png` `launch_capacity_physical.png` `launch_capacity_predictions.png` | (可选)火箭发射能力上限/趋势拟合与预测 |
#### 数据文件(如存在)
@@ -126,7 +130,22 @@
-5) 其它历史遗留图
+5) Pareto 前沿与“膝点”(组合方案 101–186 年)
+
+> 说明:`pareto_optimization.py` 当前将图片/CSV 输出到上一级目录(`../`),因此这里用相对路径引用。
+
+#### `../pareto_combined_range.png`
+
+
+
+#### `../combined_decision.png`
+
+
+
+
+
+
+6) 其它历史遗留图
本目录还包含 `earth_moon_transfer_analysis.png`(早期“地月转移:载荷–燃料/能量关系”图)。当前目录下未发现对应的 `earth_moon_transfer.py` 源码文件;如果你需要我把该脚本补回并与现有口径统一,我可以再加一个文件。
@@ -271,6 +290,33 @@ Rocket Only (219 years): Total energy: 50609 PJ
- 当工期 ≥ 186 年时,电梯即可独立完成,混合方案会自动退化为纯电梯曲线(能耗趋于同一水平)。
- 纯火箭能耗显著更高(本模型下约为纯电梯的 **3×** 量级)。
+#### 4)Pareto 前沿与“膝点”(只在组合方案 101–186 年区间内找拐点)
+
+> 这个分析回答“在可行区间内,选择多少年最划算”的决策问题:既不想太慢、也不想能耗太高。
+> 我们把目标写成双目标:**最小化完工时间 \(T\)** 与 **最小化总能量 \(E\)**,并在 \(T\in[101, 186]\) 年上寻找“边际收益开始明显递减”的拐点(knee point)。
+
+从本目录运行:
+
+```bash
+python ../pareto_optimization.py
+```
+
+会生成(输出在上一级目录 `../`):
+
+- `../pareto_combined_range.png`
+- `../combined_decision.png`
+- `../pareto_combined_range.csv`(组合区间的 Pareto 数据)
+
+**组合区间(101–186 年)推荐膝点(示例输出)**:
+
+| 推荐方法 | 年份 | 总能量 (PJ) | 电梯占比 | 火箭占比 |
+|---|---:|---:|---:|---:|
+| 最大距离法(最稳健) | **139** | **24361** | **74.6%** | **25.4%** |
+
+解读要点:
+- **101→139 年**:多给一些时间,能量下降很快(大量火箭任务转移到电梯/低纬火箭站)。
+- **139→186 年**:继续拉长工期仍能省能量,但**边际节省明显变小**;186 年达到纯电梯可独立完成的极限点。
+
---
### 发射场(10 个)与纬度效应(月球任务场景)
@@ -354,4 +400,5 @@ Rocket Only (219 years): Total energy: 50609 PJ
- **纯火箭**在“10 站 * 每日 1 发 * 125 吨/发”的约束下,最短约 **219 年**,且能耗约 **5×10⁴ PJ** 量级。
- **纯电梯**能耗最低(约 **1.57×10⁴ PJ**),但最短约 **186 年**。
- **混合方案**能把工期压到约 **101 年**,能耗介于两者之间,并随着工期增加逐步趋近纯电梯能耗。
+- **在混合方案可行区间(101–186 年)内**,用 Pareto/膝点法可给出一个“最划算”的折中点:本模型下约 **139 年**(约 **2.44×10⁴ PJ**,电梯约 **75%**、火箭约 **25%**)。
diff --git a/pareto_combined_range.csv b/pareto_combined_range.csv
new file mode 100644
index 0000000..798e4a9
--- /dev/null
+++ b/pareto_combined_range.csv
@@ -0,0 +1,301 @@
+years,energy_PJ,elevator_fraction,sites_used,rocket_launches
+101.0,31679.76095710881,0.54237,10,366104
+101.28501585047614,31620.609850415538,0.5439005351170569,10,364880
+101.57003170095227,31561.45874372226,0.5454310702341137,10,363656
+101.85504755142841,31502.241823054243,0.5469616053511706,10,362431
+102.14006340190454,31443.090716360963,0.5484921404682274,10,361207
+102.42507925238068,31383.87379569294,0.5500226755852843,10,359982
+102.71009510285683,31324.722688999667,0.5515532107023411,10,358758
+102.99511095333295,31265.571582306387,0.5530837458193979,10,357534
+103.2801268038091,31206.354661638365,0.5546142809364548,10,356309
+103.56514265428524,31147.203554945085,0.5561448160535117,10,355085
+103.85015850476137,31087.98663427707,0.5576753511705685,10,353860
+104.13517435523751,31028.835527583797,0.5592058862876255,10,352636
+104.42019020571364,30969.618606915767,0.5607364214046823,10,351411
+104.70520605618978,30910.467500222494,0.5622669565217392,10,350187
+104.99022190666592,30851.316393529218,0.563797491638796,10,348963
+105.27523775714205,30792.09947286119,0.5653280267558528,10,347738
+105.5602536076182,30733.320588222938,0.5668585618729096,9,346514
+105.84526945809434,30677.360233793443,0.5683890969899665,9,345289
+106.13028530857046,30621.464186366808,0.5699196321070233,9,344065
+106.4153011590466,30565.5038319373,0.5714501672240803,9,342840
+106.70031700952273,30509.60778451066,0.5729807023411371,9,341616
+106.98533285999888,30453.711737084028,0.574511237458194,9,340392
+107.27034871047502,30397.751382654526,0.5760417725752509,9,339167
+107.55536456095115,30341.855335227887,0.5775723076923077,9,337943
+107.84038041142729,30285.894980798384,0.5791028428093645,9,336718
+108.12539626190343,30229.99893337175,0.5806333779264214,9,335494
+108.41041211237956,30174.038578942243,0.5821639130434783,9,334269
+108.6954279628557,30118.14253151561,0.5836944481605351,9,333045
+108.98044381333183,30062.24648408897,0.5852249832775919,9,331821
+109.26545966380797,30006.28612965946,0.5867555183946488,9,330596
+109.55047551428412,29950.39008223283,0.5882860535117057,9,329372
+109.83549136476024,29894.429727803326,0.5898165886287625,9,328147
+110.12050721523639,29838.53368037669,0.5913471237458194,9,326923
+110.40552306571252,29782.561361639895,0.5928776588628762,9,325698
+110.69053891618866,29726.665314213256,0.5944081939799332,9,324474
+110.9755547666648,29671.31387329956,0.59593872909699,8,323250
+111.26057061714093,29616.757347939132,0.5974692642140468,8,322025
+111.54558646761707,29562.264447117264,0.5989997993311036,8,320801
+111.83060231809321,29507.707921756835,0.6005303344481605,8,319576
+112.11561816856934,29453.21502093497,0.6020608695652173,8,318352
+112.40063401904548,29398.658495574542,0.6035914046822742,8,317127
+112.68564986952163,29344.16559475267,0.6051219397993312,8,315903
+112.97066571999775,29289.672693930803,0.606652474916388,8,314679
+113.2556815704739,29235.116168570374,0.6081830100334449,8,313454
+113.54069742095002,29180.623267748502,0.6097135451505017,8,312230
+113.82571327142617,29126.066742388073,0.6112440802675585,8,311005
+114.11072912190231,29071.573841566213,0.6127746153846154,8,309781
+114.39574497237844,29017.017316205773,0.6143051505016722,8,308556
+114.68076082285458,28962.524415383912,0.615835685618729,8,307332
+114.96577667333071,28908.031514562044,0.6173662207357858,8,306108
+115.25079252380685,28853.474989201608,0.6188967558528428,8,304883
+115.535808374283,28798.982088379744,0.6204272909698997,8,303659
+115.82082422475912,28744.425563019315,0.6219578260869565,8,302434
+116.10584007523526,28689.932662197443,0.6234883612040134,8,301210
+116.3908559257114,28635.376136837014,0.6250188963210702,8,299985
+116.67587177618753,28580.88323601515,0.6265494314381271,8,298761
+116.96088762666368,28526.45534360956,0.6280799665551839,7,297537
+117.24590347713982,28471.997009615712,0.6296105016722408,7,296312
+117.53091932761595,28417.60224988323,0.6311410367892976,7,295088
+117.81593517809209,28363.14391588938,0.6326715719063545,7,293863
+118.10095102856822,28308.7491561569,0.6342021070234113,7,292639
+118.38596687904436,28254.290822163046,0.6357326421404682,7,291414
+118.67098272952049,28199.896062430566,0.637263177257525,7,290190
+118.95599857999663,28145.50130269808,0.6387937123745819,7,288966
+119.24101443047277,28091.042968704238,0.6403242474916389,7,287741
+119.5260302809489,28036.64205631926,0.6418547826086957,7,286517
+119.81104613142504,27982.18372232541,0.6433853177257525,7,285292
+120.09606198190119,27927.78896259293,0.6449158528428094,7,284068
+120.38107783237731,27873.330628599077,0.6464463879598662,7,282843
+120.66609368285346,27818.935868866593,0.647976923076923,7,281619
+120.9511095333296,27764.541109134116,0.6495074581939799,7,280395
+121.23612538380573,27710.08277514026,0.6510379933110367,7,279170
+121.52114123428187,27655.68801540778,0.6525685284280937,7,277946
+121.806157084758,27601.22968141393,0.6540990635451505,7,276721
+122.09117293523414,27546.834921681453,0.6556295986622074,7,275497
+122.37618878571027,27492.3765876876,0.6571601337792642,7,274272
+122.66120463618641,27437.981827955115,0.6586906688963211,7,273048
+122.94622048666255,27383.587068222638,0.6602212040133779,7,271824
+123.23123633713868,27329.128734228783,0.6617517391304347,7,270599
+123.51625218761482,27274.84492702738,0.6632822742474915,6,269375
+123.80126803809097,27220.569274449346,0.6648128093645485,6,268150
+124.0862838885671,27166.357097332555,0.6663433444816053,6,266926
+124.37129973904324,27112.081444754524,0.6678738795986622,6,265701
+124.65631558951938,27057.869267637732,0.6694044147157191,6,264477
+124.94133143999551,27003.65709052094,0.6709349498327759,6,263253
+125.22634729047165,26949.381437942906,0.6724654849498327,6,262028
+125.5113631409478,26895.169260826126,0.6739960200668896,6,260804
+125.79637899142392,26840.893608248083,0.6755265551839464,6,259579
+126.08139484190006,26786.681431131296,0.6770570903010034,6,258355
+126.36641069237619,26732.405778553257,0.6785876254180602,6,257130
+126.65142654285233,26678.193601436476,0.6801181605351171,6,255906
+126.93644239332846,26623.981424319685,0.6816486956521739,6,254682
+127.2214582438046,26569.705771741646,0.6831792307692307,6,253457
+127.50647409428075,26515.49359462486,0.6847097658862875,6,252233
+127.79148994475688,26461.21794204682,0.6862403010033443,6,251008
+128.076505795233,26407.005764930036,0.6877708361204011,6,249784
+128.36152164570916,26352.730112351997,0.6893013712374582,6,248559
+128.6465374961853,26298.51793523521,0.690831906354515,6,247335
+128.93155334666142,26244.300198266632,0.6923624414715718,6,246111
+129.21656919713757,26190.024545688593,0.6938929765886287,6,244886
+129.5015850476137,26135.81236857181,0.6954235117056855,6,243662
+129.78660089808983,26081.536715993767,0.6969540468227424,6,242437
+130.071616748566,26027.324538876983,0.6984845819397993,6,241213
+130.35663259904211,25973.048886298948,0.7000151170568562,6,239988
+130.64164844951824,25918.836709182156,0.701545652173913,6,238764
+130.9266642999944,25865.032259204538,0.70307618729097,5,237540
+131.21168015047053,25811.2648093821,0.7046067224080268,5,236315
+131.49669600094666,25757.56054378724,0.7061372575250836,5,235091
+131.7817118514228,25703.793093964807,0.7076677926421405,5,233866
+132.06672770189894,25650.088828369953,0.7091983277591973,5,232642
+132.35174355237507,25596.321378547516,0.7107288628762541,5,231417
+132.63675940285123,25542.61711295266,0.7122593979933112,5,230193
+132.92177525332735,25488.912847357806,0.713789933110368,5,228969
+133.20679110380348,25435.14539753536,0.7153204682274248,5,227744
+133.49180695427964,25381.441131940504,0.7168510033444816,5,226520
+133.77682280475577,25327.673682118068,0.7183815384615384,5,225295
+134.0618386552319,25273.96941652321,0.7199120735785952,5,224071
+134.34685450570802,25220.201966700773,0.721442608695652,5,222846
+134.63187035618418,25166.497701105916,0.722973143812709,5,221622
+134.9168862066603,25112.793435511063,0.7245036789297659,5,220398
+135.20190205713644,25059.025985688626,0.7260342140468227,5,219173
+135.48691790761256,25005.321720093765,0.7275647491638795,5,217949
+135.77193375808872,24951.554270271332,0.7290952842809364,5,216724
+136.05694960856485,24897.85000467648,0.7306258193979932,5,215500
+136.34196545904098,24844.082554854034,0.73215635451505,5,214275
+136.62698130951713,24790.378289259184,0.733686889632107,5,213051
+136.91199715999326,24736.67402366432,0.7352174247491639,5,211827
+137.1970130104694,24682.906573841883,0.7367479598662207,5,210602
+137.48202886094555,24629.202308247033,0.7382784949832776,5,209378
+137.76704471142168,24575.434858424593,0.7398090301003344,5,208153
+138.0520605618978,24521.726489146295,0.7413395652173913,5,206929
+138.33707641237396,24467.95903932386,0.7428701003344482,5,205704
+138.6220922628501,24414.254773729,0.744400635451505,5,204480
+138.90710811332622,24360.550508134147,0.7459311705685618,5,203256
+139.19212396380237,24307.39243233035,0.7474617056856189,4,202031
+139.4771398142785,24254.528682623262,0.7489922408026755,4,200807
+139.76215566475463,24201.602261198288,0.7505227759197323,4,199582
+140.0471715152308,24148.738511491203,0.7520533110367893,4,198358
+140.33218736570691,24095.812090066232,0.7535838461538461,4,197133
+140.61720321618304,24042.948340359148,0.7551143812709029,4,195909
+140.9022190666592,23990.084590652063,0.7566449163879598,4,194685
+141.18723491713533,23937.158169227092,0.7581754515050166,4,193460
+141.47225076761146,23884.29441952001,0.7597059866220734,4,192236
+141.7572666180876,23831.367998095036,0.7612365217391305,4,191011
+142.04228246856374,23778.50424838795,0.7627670568561873,4,189787
+142.32729831903987,23725.577826962977,0.7642975919732441,4,188562
+142.612314169516,23672.714077255892,0.7658281270903009,4,187338
+142.89733001999215,23619.850327548815,0.7673586622073579,4,186114
+143.18234587046828,23566.92390612384,0.7688891973244147,4,184889
+143.4673617209444,23514.060156416752,0.7704197324414715,4,183665
+143.75237757142054,23461.133734991778,0.7719502675585284,4,182440
+144.0373934218967,23408.269985284693,0.7734808026755853,4,181216
+144.32240927237282,23355.343563859722,0.7750113377926421,4,179991
+144.60742512284895,23302.479814152637,0.7765418729096989,4,178767
+144.8924409733251,23249.616064445556,0.7780724080267559,4,177543
+145.17745682380124,23196.68964302058,0.7796029431438126,4,176318
+145.46247267427736,23143.825893313497,0.7811334782608694,4,175094
+145.74748852475352,23090.899471888522,0.7826640133779263,4,173869
+146.03250437522965,23038.035722181445,0.7841945484949832,4,172645
+146.31752022570578,22985.109300756463,0.78572508361204,4,171420
+146.60253607618193,22932.245551049386,0.787255618729097,4,170196
+146.88755192665806,22879.3818013423,0.7887861538461538,4,168972
+147.1725677771342,22826.455379917323,0.7903166889632106,4,167747
+147.45758362761035,22773.589576565555,0.7918472240802675,4,166523
+147.74259947808648,22720.66315514058,0.7933777591973243,4,165298
+148.0276153285626,22667.799405433496,0.7949082943143811,4,164074
+148.31263117903876,22614.872984008525,0.7964388294314382,4,162849
+148.5976470295149,22562.204830829458,0.797969364548495,3,161625
+148.88266287999102,22509.616963828565,0.7994998996655518,3,160401
+149.16767873046717,22456.966604720914,0.8010304347826087,3,159176
+149.4526945809433,22404.378737720017,0.8025609698996655,3,157952
+149.73771043141943,22351.728378612366,0.8040915050167223,3,156727
+150.0227262818956,22299.140511611473,0.8056220401337794,3,155503
+150.30774213237171,22246.490152503826,0.8071525752508362,3,154278
+150.59275798284784,22193.902285502925,0.808683110367893,3,153054
+150.87777383332397,22141.314418502032,0.8102136454849498,3,151830
+151.16278968380013,22088.66405939438,0.8117441806020066,3,150605
+151.44780553427626,22036.076192393488,0.8132747157190634,3,149381
+151.73282138475238,21983.425833285837,0.8148052508361202,3,148156
+152.0178372352285,21930.83796628494,0.816335785953177,3,146932
+152.30285308570467,21878.18760717729,0.817866321070234,3,145707
+152.5878689361808,21825.599740176393,0.8193968561872909,3,144483
+152.87288478665693,21773.011873175496,0.8209273913043477,3,143259
+153.15790063713308,21720.36151406785,0.8224579264214046,3,142034
+153.4429164876092,21667.77364706695,0.8239884615384614,3,140810
+153.72793233808534,21615.1232879593,0.8255189966555183,3,139585
+154.0129481885615,21562.535420958407,0.8270495317725752,3,138361
+154.29796403903762,21509.885061850757,0.828580066889632,3,137136
+154.58297988951375,21457.297194849863,0.8301106020066888,3,135912
+154.8679957399899,21404.709327848966,0.8316411371237459,3,134688
+155.15301159046604,21352.058968741316,0.8331716722408027,3,133463
+155.43802744094216,21299.471101740422,0.8347022073578595,3,132239
+155.72304329141832,21246.82074263277,0.8362327424749164,3,131014
+156.00805914189445,21194.232875631875,0.8377632775919732,3,129790
+156.29307499237058,21141.582516524224,0.83929381270903,3,128565
+156.57809084284673,21088.993134712055,0.8408243478260871,3,127341
+156.86310669332286,21036.405267711158,0.8423548829431439,3,126117
+157.148122543799,20983.754908603503,0.8438854180602006,3,124892
+157.43313839427515,20931.16704160261,0.8454159531772575,3,123668
+157.71815424475128,20878.51668249496,0.8469464882943143,3,122443
+158.0031700952274,20825.928815494062,0.8484770234113711,3,121219
+158.28818594570356,20773.27845638641,0.8500075585284281,3,119994
+158.5732017961797,20720.69058938552,0.8515380936454849,3,118770
+158.85821764665582,20668.10272238462,0.8530686287625417,3,117546
+159.14323349713194,20615.45236327697,0.8545991638795986,3,116321
+159.4282493476081,20563.69918904202,0.8561296989966555,2,115097
+159.71326519808423,20511.979658520824,0.8576602341137123,2,113872
+159.99828104856036,20460.321970540033,0.8591907692307691,2,112648
+160.2832968990365,20408.602440018836,0.8607213043478259,2,111423
+160.56831274951264,20356.944752038045,0.8622518394648829,2,110199
+160.85332859998877,20305.28706405725,0.8637823745819397,2,108975
+161.1383444504649,20253.567533536054,0.8653129096989965,2,107750
+161.42336030094106,20201.909845555263,0.8668434448160535,2,106526
+161.70837615141718,20150.190315034066,0.8683739799331103,2,105301
+161.9933920018933,20098.53262705327,0.8699045150501672,2,104077
+162.27840785236947,20046.81309653208,0.8714350501672241,2,102852
+162.5634237028456,19995.155408551283,0.8729655852842809,2,101628
+162.84843955332173,19943.497720570493,0.8744961204013376,2,100404
+163.13345540379788,19891.778190049296,0.8760266555183945,2,99179
+163.418471254274,19840.1205020685,0.8775571906354513,2,97955
+163.70348710475014,19788.400971547308,0.8790877257525082,2,96730
+163.9885029552263,19736.743283566517,0.8806182608695652,2,95506
+164.27351880570242,19685.02375304532,0.882148795986622,2,94281
+164.55853465617855,19633.366065064525,0.8836793311036788,2,93057
+164.8435505066547,19581.708377083734,0.8852098662207357,2,91833
+165.12856635713084,19529.98884656254,0.8867404013377925,2,90608
+165.41358220760696,19478.331158581743,0.8882709364548494,2,89384
+165.69859805808312,19426.611628060553,0.8898014715719064,2,88159
+165.98361390855925,19374.953724401188,0.8913320066889632,2,86935
+166.26862975903538,19323.234193879995,0.89286254180602,2,85710
+166.55364560951153,19271.576505899204,0.894393076923077,2,84486
+166.83866145998763,19219.918817918406,0.8959236120401336,2,83262
+167.1236773104638,19168.199287397212,0.8974541471571906,2,82037
+167.40869316093995,19116.54159941642,0.8989846822742475,2,80813
+167.69370901141605,19064.822068895224,0.9005152173913042,2,79588
+167.9787248618922,19013.16438091443,0.9020457525083612,2,78364
+168.26374071236836,18961.44485039324,0.9035762876254182,2,77139
+168.54875656284446,18909.787162412442,0.9051068227424748,2,75915
+168.83377241332062,18858.129474431647,0.9066373578595316,2,74691
+169.11878826379674,18806.409943910454,0.9081678929765885,2,73466
+169.40380411427287,18754.75225592966,0.9096984280936453,2,72242
+169.68881996474903,18703.032725408466,0.9112289632107022,2,71017
+169.97383581522516,18651.37503742767,0.912759498327759,2,69793
+170.2588516657013,18599.655506906478,0.9142900334448159,2,68568
+170.54386751617744,18547.997818925687,0.9158205685618729,2,67344
+170.82888336665357,18496.34013094489,0.9173511036789297,2,66120
+171.1138992171297,18444.620600423696,0.9188816387959865,2,64895
+171.39891506760586,18392.962912442905,0.9204121739130434,2,63671
+171.68393091808198,18341.290399849782,0.9219427090301002,1,62446
+171.9689467685581,18289.91913300881,0.923473244147157,1,61222
+172.25396261903427,18238.48623930601,0.9250037792642141,1,59997
+172.5389784695104,18187.11497246504,0.9265343143812709,1,58773
+172.82399431998653,18135.743705624067,0.9280648494983277,1,57549
+173.10901017046268,18084.31081192127,0.9295953846153846,1,56324
+173.3940260209388,18032.9395450803,0.9311259197324414,1,55100
+173.67904187141494,17981.506651377495,0.9326564548494982,1,53875
+173.9640577218911,17930.135384536527,0.9341869899665552,1,52651
+174.24907357236722,17878.702490833723,0.9357175250836121,1,51426
+174.53408942284335,17827.33122399275,0.9372480602006688,1,50202
+174.8191052733195,17775.959957151783,0.9387785953177257,1,48978
+175.1041211237956,17724.52706344898,0.9403091304347824,1,47753
+175.38913697427176,17673.155796608007,0.9418396655518393,1,46529
+175.67415282474792,17621.72290290521,0.9433702006688963,1,45304
+175.95916867522402,17570.351636064235,0.9449007357859529,1,44080
+176.24418452570018,17518.918742361435,0.9464312709030099,1,42855
+176.52920037617633,17467.547475520467,0.9479618060200669,1,41631
+176.81421622665243,17416.176208679495,0.9494923411371236,1,40407
+177.0992320771286,17364.74331497669,0.9510228762541805,1,39182
+177.38424792760472,17313.37204813572,0.9525534113712374,1,37958
+177.66926377808085,17261.93915443292,0.9540839464882942,1,36733
+177.954279628557,17210.56788759195,0.9556144816053511,1,35509
+178.23929547903313,17159.134993889147,0.9571450167224079,1,34284
+178.52431132950926,17107.76372704818,0.9586755518394647,1,33060
+178.80932717998542,17056.392460207207,0.9602060869565218,1,31836
+179.09434303046154,17004.959566504403,0.9617366220735786,1,30611
+179.37935888093767,16953.588299663435,0.9632671571906354,1,29387
+179.66437473141383,16902.155405960635,0.9647976923076923,1,28162
+179.94939058188996,16850.784139119663,0.9663282274247491,1,26938
+180.2344064323661,16799.35124541686,0.9678587625418059,1,25713
+180.51942228284224,16747.97997857589,0.9693892976588627,1,24489
+180.80443813331837,16696.60871173492,0.9709198327759195,1,23265
+181.0894539837945,16645.175818032116,0.9724503678929765,1,22040
+181.37446983427066,16593.804551191148,0.9739809030100334,1,20816
+181.65948568474678,16542.371657488344,0.9755114381270902,1,19591
+181.9445015352229,16491.000390647372,0.977041973244147,1,18367
+182.22951738569907,16439.567496944575,0.978572508361204,1,17142
+182.5145332361752,16388.196230103604,0.9801030434782608,1,15918
+182.79954908665133,16336.824963262632,0.9816335785953176,1,14694
+183.08456493712748,16285.392069559832,0.9831641137123746,1,13469
+183.36958078760358,16234.02080271886,0.9846946488294313,1,12245
+183.65459663807974,16182.587909016058,0.9862251839464882,1,11020
+183.9396124885559,16131.21664217509,0.9877557190635452,1,9796
+184.224628339032,16079.783748472286,0.9892862541806018,1,8571
+184.50964418950815,16028.412481631316,0.9908167892976588,1,7347
+184.7946600399843,15977.041214790348,0.9923473244147157,1,6123
+185.0796758904604,15925.608321087544,0.9938778595317724,1,4898
+185.36469174093656,15874.237054246574,0.9954083946488294,1,3674
+185.6497075914127,15822.80416054377,0.9969389297658863,1,2449
+185.93472344188882,15771.432893702802,0.9984694648829431,1,1225
+186.21973929236498,15720.0,1.0,0,0
diff --git a/pareto_combined_range.png b/pareto_combined_range.png
new file mode 100644
index 0000000..26756df
Binary files /dev/null and b/pareto_combined_range.png differ
diff --git a/pareto_front_data.csv b/pareto_front_data.csv
new file mode 100644
index 0000000..41d47ac
--- /dev/null
+++ b/pareto_front_data.csv
@@ -0,0 +1,499 @@
+years,energy_PJ,elevator_fraction,sites_used,rocket_launches
+100.80160320641282,31721.015809442823,0.5413046092184368,10,366957
+101.20240480961924,31637.791423463375,0.5434569138276553,10,365235
+101.60320641282566,31554.5415104297,0.5456092184368738,10,363513
+102.00400801603206,31471.317124450256,0.5477615230460922,10,361791
+102.40480961923848,31388.092738470812,0.5499138276553106,10,360069
+102.8056112224449,31304.908639411875,0.552066132264529,10,358348
+103.2064128256513,31221.684253432435,0.5542184368737475,10,356626
+103.60721442885772,31138.459867452988,0.556370741482966,10,354904
+104.00801603206413,31055.235481473548,0.5585230460921844,10,353182
+104.40881763527054,30971.98556843986,0.5606753507014028,10,351460
+104.80961923847696,30888.761182460417,0.5628276553106213,10,349738
+105.21042084168337,30805.602610455724,0.5649799599198397,10,348017
+105.61122244488978,30723.31263851132,0.5671322645290581,9,346295
+106.0120240480962,30644.663419167766,0.5692845691382766,9,344573
+106.41282565130261,30566.014199824203,0.5714368737474951,9,342851
+106.81362725450902,30487.364980480637,0.5735891783567134,9,341129
+107.21442885771543,30408.703796829792,0.5757414829659319,9,339407
+107.61523046092185,30330.054577486233,0.5778937875751503,9,337685
+108.01603206412825,30251.469665145545,0.5800460921843688,9,335964
+108.41683366733467,30172.808481494696,0.5821983967935872,9,334242
+108.81763527054107,30094.15926215113,0.5843507014028057,9,332520
+109.21843687374749,30015.510042807575,0.5865030060120241,9,330798
+109.61923847695391,29936.84885915672,0.5886553106212425,9,329076
+110.02004008016033,29858.19963981316,0.590807615230461,9,327354
+110.42084168336673,29779.614727472468,0.5929599198396793,9,325633
+110.82164328657315,29700.965508128913,0.5951122244488979,9,323911
+111.22244488977955,29624.06439991274,0.5972645290581162,8,322189
+111.62324649298597,29547.38750240176,0.5994168336673347,8,320467
+112.02404809619239,29470.710604890784,0.6015691382765531,8,318745
+112.4248496993988,29394.027202786925,0.6037214428857716,8,317023
+112.82565130260521,29317.41392981451,0.60587374749499,8,315302
+113.22645290581163,29240.73703230353,0.6080260521042085,8,313580
+113.62725450901803,29164.060134792555,0.6101783567134269,8,311858
+114.02805611222445,29087.376732688695,0.6123306613226454,8,310136
+114.42885771543087,29010.699835177722,0.6144829659318638,8,308414
+114.82965931863727,28934.02293766674,0.6166352705410821,8,306692
+115.23046092184369,28857.33953556288,0.6187875751503006,8,304970
+115.6312625250501,28780.726262590473,0.620939879759519,8,303249
+116.03206412825651,28704.049365079496,0.6230921843687374,8,301527
+116.43286573146293,28627.37246756852,0.6252444889779559,8,299805
+116.83366733466934,28550.71043327357,0.6273967935871744,7,298083
+117.23446893787576,28474.171496392344,0.6295490981963928,7,296361
+117.63527054108216,28397.63255951111,0.6317014028056112,7,294639
+118.03607214428858,28321.151044238755,0.6338537074148296,7,292918
+118.43687374749499,28244.612107357527,0.636006012024048,7,291196
+118.8376753507014,28168.073170476295,0.6381583166332665,7,289474
+119.23847695390782,28091.52808094257,0.640310621242485,7,287752
+119.63927855711422,28014.989144061346,0.6424629258517034,7,286030
+120.04008016032064,27938.45020718012,0.6446152304609218,7,284308
+120.44088176352706,27861.911270298886,0.6467675350701403,7,282586
+120.84168336673346,27785.429755026526,0.6489198396793586,7,280865
+121.24248496993988,27708.890818145297,0.6510721442885772,7,279143
+121.6432865731463,27632.351881264072,0.6532244488977956,7,277421
+122.0440881763527,27555.80679173035,0.655376753507014,7,275699
+122.44488977955912,27479.267854849113,0.6575290581162324,7,273977
+122.84569138276554,27402.72891796789,0.659681362725451,7,272255
+123.24649298597194,27326.25355534803,0.6618336673346693,7,270534
+123.64729458917836,27249.903497244704,0.6639859719438878,6,268812
+124.04809619238478,27173.62124306671,0.6661382765531062,6,267090
+124.44889779559118,27097.338988888714,0.6682905811623246,6,265368
+124.8496993987976,27021.051174858923,0.6704428857715431,6,263646
+125.250501002004,26944.76892068093,0.6725951903807614,6,261924
+125.65130260521042,26868.55014196419,0.67474749498998,6,260203
+126.05210420841684,26792.262327934397,0.6768997995991984,6,258481
+126.45290581162325,26715.980073756407,0.6790521042084169,6,256759
+126.85370741482966,26639.69781957841,0.6812044088176353,6,255037
+127.25450901803607,26563.415565400413,0.6833567134268537,6,253315
+127.65531062124248,26487.127751370626,0.6855090180360721,6,251593
+128.0561122244489,26410.84549719263,0.6876613226452907,6,249871
+128.4569138276553,26334.626718475887,0.689813627254509,6,248150
+128.85771543086173,26258.3389044461,0.6919659318637276,6,246428
+129.25851703406815,26182.0566502681,0.6941182364729459,6,244706
+129.65931863727454,26105.774396090103,0.6962705410821642,6,242984
+130.06012024048096,26029.49214191211,0.6984228456913827,6,241262
+130.46092184368737,25953.20432788232,0.7005751503006011,6,239540
+130.8617234468938,25877.277074070076,0.7027274549098197,5,237819
+131.2625250501002,25801.708924852963,0.704879759519038,5,236097
+131.66332665330663,25726.13667195241,0.7070320641282566,5,234375
+132.06412825651302,25650.568522735288,0.7091843687374748,5,232653
+132.46492985971943,25575.00037351817,0.7113366733466934,5,230931
+132.86573146292585,25499.432224301057,0.7134889779559117,5,229209
+133.26653306613227,25423.8599714005,0.7156412825651303,5,227487
+133.6673346693387,25348.355006410962,0.7177935871743487,5,225766
+134.06813627254508,25272.78685719384,0.719945891783567,5,224044
+134.46893787575152,25197.214604293287,0.7220981963927856,5,222322
+134.8697394789579,25121.646455076167,0.7242505010020039,5,220600
+135.27054108216433,25046.078305859057,0.7264028056112224,5,218878
+135.67134268537075,24970.506052958503,0.728555110220441,5,217156
+136.07214428857716,24895.00108796896,0.7307074148296593,5,215435
+136.47294589178358,24819.432938751845,0.7328597194388778,5,213713
+136.87374749498997,24743.864789534724,0.7350120240480961,5,211991
+137.2745490981964,24668.29253663417,0.7371643286573146,5,210269
+137.6753507014028,24592.724387417056,0.739316633266533,5,208547
+138.07615230460922,24517.15623819994,0.7414689378757515,5,206825
+138.47695390781564,24441.647169526965,0.74362124248497,5,205104
+138.87775551102203,24366.079020309848,0.7457735470941883,5,203382
+139.27855711422845,24291.37393740676,0.7479258517034068,4,201660
+139.67935871743487,24216.987635529404,0.7500781563126253,4,199938
+140.08016032064128,24142.599280007358,0.7522304609218436,4,198216
+140.4809619238477,24068.212978130003,0.7543827655310622,4,196494
+140.88176352705412,23993.826676252647,0.7565350701402805,4,194772
+141.28256513026054,23919.500992448495,0.7586873747494991,4,193051
+141.68336673346693,23845.114690571136,0.7608396793587173,4,191329
+142.08416833667334,23770.728388693777,0.7629919839679359,4,189607
+142.48496993987976,23696.340033171735,0.7651442885771543,4,187885
+142.88577154308618,23621.95373129438,0.7672965931863728,4,186163
+143.2865731462926,23547.567429417024,0.7694488977955912,4,184441
+143.68737474949899,23473.243799257554,0.7716012024048096,4,182720
+144.0881763527054,23398.855443735512,0.773753507014028,4,180998
+144.48897795591182,23324.469141858157,0.7759058116232466,4,179276
+144.88977955911824,23250.0828399808,0.7780581162324649,4,177554
+145.29058116232466,23175.69448445876,0.7802104208416835,4,175832
+145.69138276553107,23101.308182581404,0.7823627254509018,4,174110
+146.0921843687375,23026.92188070405,0.7845150300601204,4,172388
+146.49298597194388,22952.59825054458,0.7866673346693386,4,170667
+146.8937875751503,22878.209895022537,0.7888196392785571,4,168945
+147.29458917835672,22803.82359314518,0.7909719438877756,4,167223
+147.69539078156313,22729.437291267826,0.793124248496994,4,165501
+148.09619238476955,22655.04893574578,0.7952765531062125,4,163779
+148.49699398797594,22580.760701549138,0.7974288577154308,3,162057
+148.89779559118236,22506.82485183411,0.7995811623246493,3,160336
+149.29859719438878,22432.826510012335,0.8017334669338678,3,158614
+149.6993987975952,22358.826653379274,0.8038857715430862,3,156892
+150.1002004008016,22284.828311557496,0.8060380761523047,3,155170
+150.501002004008,22210.829969735714,0.8081903807615229,3,153448
+150.90180360721445,22136.83011310266,0.8103426853707416,3,151726
+151.30260521042084,22062.894263387632,0.8124949899799598,3,150005
+151.70340681362725,21988.89592156585,0.8146472945891784,3,148283
+152.10420841683367,21914.89606493279,0.8167995991983968,3,146561
+152.5050100200401,21840.897723111015,0.8189519038076153,3,144839
+152.9058116232465,21766.899381289233,0.8211042084168337,3,143117
+153.3066132264529,21692.901039467455,0.8232565130260521,3,141395
+153.7074148296593,21618.901182834394,0.8254088176352705,3,139673
+154.10821643286573,21544.96533311937,0.8275611222444891,3,137952
+154.50901803607215,21470.966991297588,0.8297134268537074,3,136230
+154.90981963927857,21396.967134664534,0.831865731462926,3,134508
+155.31062124248496,21322.96879284275,0.8340180360721442,3,132786
+155.7114228456914,21248.97045102097,0.8361703406813629,3,131064
+156.1122244488978,21174.97210919919,0.8383226452905811,3,129342
+156.5130260521042,21101.03474467289,0.8404749498997997,3,127621
+156.91382765531063,21027.036402851107,0.8426272545090181,3,125899
+157.31462925851704,20953.038061029325,0.8447795591182367,3,124177
+157.71543086172346,20879.03820439627,0.846931863727455,3,122455
+158.11623246492985,20805.039862574486,0.8490841683366732,3,120733
+158.51703406813627,20731.041520752704,0.8512364729458918,3,119011
+158.9178356713427,20657.04166411965,0.8533887775551101,3,117289
+159.3186372745491,20583.582596109063,0.8555410821643287,2,115568
+159.71943887775552,20510.89248092589,0.8576933867735471,2,113846
+160.1202404809619,20438.202365742713,0.8598456913827655,2,112124
+160.52104208416833,20365.512034880972,0.8619979959919839,2,110402
+160.92184368737475,20292.8219196978,0.8641503006012023,2,108680
+161.32264529058116,20220.13180451462,0.8663026052104208,2,106958
+161.72344689378758,20147.50331619328,0.8684549098196394,2,105237
+162.124248496994,20074.813201010107,0.8706072144288577,2,103515
+162.52505010020042,20002.123085826937,0.8727595190380762,2,101793
+162.9258517034068,19929.43297064376,0.8749118236472945,2,100071
+163.32665330661322,19856.74263978202,0.877064128256513,2,98349
+163.72745490981964,19784.052524598847,0.8792164328657314,2,96627
+164.12825651302606,19711.424251956072,0.8813687374749499,2,94906
+164.52905811623248,19638.73392109433,0.8835210420841684,2,93184
+164.92985971943887,19566.043805911155,0.8856733466933867,2,91462
+165.3306613226453,19493.35369072798,0.8878256513026053,2,89740
+165.7314629258517,19420.663575544808,0.8899779559118237,2,88018
+166.13226452905812,19347.973244683064,0.892130260521042,2,86296
+166.53306613226454,19275.283129499894,0.8942825651302606,2,84574
+166.93386773547093,19202.654856857116,0.8964348697394788,2,82853
+167.33466933867737,19129.964525995376,0.8985871743486975,2,81131
+167.73547094188376,19057.2744108122,0.9007394789579157,2,79409
+168.13627254509018,18984.58429562903,0.9028917835671343,2,77687
+168.5370741482966,18911.893964767285,0.9050440881763527,2,75965
+168.93787575150301,18839.20384958411,0.9071963927855712,2,74243
+169.33867735470943,18766.575576941337,0.9093486973947896,2,72522
+169.73947895791582,18693.885461758164,0.911501002004008,2,70800
+170.14028056112227,18621.19513089642,0.9136533066132265,2,69078
+170.54108216432866,18548.505015713246,0.915805611222445,2,67356
+170.94188376753507,18475.814900530073,0.9179579158316633,2,65634
+171.3426853707415,18403.124569668333,0.9201102204408819,2,63912
+171.74348697394788,18330.54143105545,0.9222625250501001,1,62190
+172.14428857715433,18258.315830301275,0.9244148296593188,1,60469
+172.54509018036072,18186.02860268526,0.926567134268537,1,58747
+172.94589178356713,18113.741375069247,0.9287194388777555,1,57025
+173.34669338677355,18041.454147453234,0.930871743486974,1,55303
+173.74749498997994,17969.166919837222,0.9330240480961923,1,53581
+174.1482965931864,17896.87969222121,0.9351763527054109,1,51859
+174.54909819639278,17824.65409146703,0.9373286573146292,1,50138
+174.9498997995992,17752.366863851017,0.9394809619238477,1,48416
+175.3507014028056,17680.079636235005,0.9416332665330662,1,46694
+175.75150300601203,17607.792408618992,0.9437855711422846,1,44972
+176.15230460921845,17535.50518100298,0.9459378757515031,1,43250
+176.55310621242484,17463.217953386964,0.9480901803607213,1,41528
+176.95390781563128,17390.992352632788,0.95024248496994,1,39807
+177.35470941883767,17318.70512501677,0.9523947895791582,1,38085
+177.7555110220441,17246.41789740076,0.9545470941883768,1,36363
+178.1563126252505,17174.130669784747,0.9566993987975952,1,34641
+178.5571142284569,17101.843442168734,0.9588517034068136,1,32919
+178.95791583166334,17029.556214552722,0.9610040080160321,1,31197
+179.35871743486973,16957.26898693671,0.9631563126252505,1,29475
+179.75951903807615,16885.043386182526,0.9653086172344689,1,27754
+180.16032064128257,16812.756158566517,0.9674609218436875,1,26032
+180.56112224448898,16740.468930950505,0.9696132264529058,1,24310
+180.9619238476954,16668.181703334492,0.9717655310621244,1,22588
+181.3627254509018,16595.894475718476,0.9739178356713426,1,20866
+181.76352705410824,16523.607248102468,0.9760701402805613,1,19144
+182.16432865731463,16451.381647348284,0.9782224448897795,1,17423
+182.56513026052104,16379.094419732273,0.980374749498998,1,15701
+182.96593186372746,16306.80719211626,0.9825270541082165,1,13979
+183.36673346693385,16234.519964500247,0.9846793587174348,1,12257
+183.7675350701403,16162.232736884236,0.9868316633266534,1,10535
+184.1683366733467,16089.945509268222,0.9889839679358717,1,8813
+184.5691382765531,16017.65828165221,0.9911362725450902,1,7091
+184.96993987975952,15945.43268089803,0.9932885771543087,1,5370
+185.37074148296594,15873.145453282015,0.9954408817635271,1,3648
+185.77154308617236,15800.858225666005,0.9975931863727456,1,1926
+186.17234468937875,15728.570998049989,0.9997454909819639,1,204
+186.5731462925852,15720.0,1.0,0,0
+186.97394789579158,15720.0,1.0,0,0
+187.374749498998,15720.0,1.0,0,0
+187.77555110220442,15720.0,1.0,0,0
+188.1763527054108,15720.0,1.0,0,0
+188.57715430861725,15720.0,1.0,0,0
+188.97795591182364,15720.0,1.0,0,0
+189.37875751503006,15720.0,1.0,0,0
+189.77955911823648,15720.0,1.0,0,0
+190.1803607214429,15720.0,1.0,0,0
+190.5811623246493,15720.0,1.0,0,0
+190.9819639278557,15720.0,1.0,0,0
+191.38276553106215,15720.0,1.0,0,0
+191.78356713426854,15720.0,1.0,0,0
+192.18436873747495,15720.0,1.0,0,0
+192.58517034068137,15720.0,1.0,0,0
+192.98597194388776,15720.0,1.0,0,0
+193.3867735470942,15720.0,1.0,0,0
+193.7875751503006,15720.0,1.0,0,0
+194.18837675350701,15720.0,1.0,0,0
+194.58917835671343,15720.0,1.0,0,0
+194.98997995991982,15720.0,1.0,0,0
+195.39078156312627,15720.0,1.0,0,0
+195.79158316633266,15720.0,1.0,0,0
+196.19238476953907,15720.0,1.0,0,0
+196.5931863727455,15720.0,1.0,0,0
+196.9939879759519,15720.0,1.0,0,0
+197.39478957915833,15720.0,1.0,0,0
+197.79559118236472,15720.0,1.0,0,0
+198.19639278557116,15720.0,1.0,0,0
+198.59719438877755,15720.0,1.0,0,0
+198.99799599198397,15720.0,1.0,0,0
+199.3987975951904,15720.0,1.0,0,0
+199.79959919839678,15720.0,1.0,0,0
+200.20040080160322,15720.0,1.0,0,0
+200.6012024048096,15720.0,1.0,0,0
+201.00200400801603,15720.0,1.0,0,0
+201.40280561122245,15720.0,1.0,0,0
+201.80360721442887,15720.0,1.0,0,0
+202.20440881763528,15720.0,1.0,0,0
+202.60521042084167,15720.0,1.0,0,0
+203.00601202404812,15720.0,1.0,0,0
+203.4068136272545,15720.0,1.0,0,0
+203.80761523046093,15720.0,1.0,0,0
+204.20841683366734,15720.0,1.0,0,0
+204.60921843687373,15720.0,1.0,0,0
+205.01002004008018,15720.0,1.0,0,0
+205.41082164328657,15720.0,1.0,0,0
+205.81162324649299,15720.0,1.0,0,0
+206.2124248496994,15720.0,1.0,0,0
+206.61322645290582,15720.0,1.0,0,0
+207.01402805611224,15720.0,1.0,0,0
+207.41482965931863,15720.0,1.0,0,0
+207.81563126252507,15720.0,1.0,0,0
+208.21643286573146,15720.0,1.0,0,0
+208.61723446893788,15720.0,1.0,0,0
+209.0180360721443,15720.0,1.0,0,0
+209.4188376753507,15720.0,1.0,0,0
+209.81963927855713,15720.0,1.0,0,0
+210.22044088176352,15720.0,1.0,0,0
+210.62124248496994,15720.0,1.0,0,0
+211.02204408817636,15720.0,1.0,0,0
+211.42284569138278,15720.0,1.0,0,0
+211.8236472945892,15720.0,1.0,0,0
+212.22444889779558,15720.0,1.0,0,0
+212.62525050100203,15720.0,1.0,0,0
+213.02605210420842,15720.0,1.0,0,0
+213.42685370741484,15720.0,1.0,0,0
+213.82765531062125,15720.0,1.0,0,0
+214.22845691382764,15720.0,1.0,0,0
+214.6292585170341,15720.0,1.0,0,0
+215.03006012024048,15720.0,1.0,0,0
+215.4308617234469,15720.0,1.0,0,0
+215.8316633266533,15720.0,1.0,0,0
+216.2324649298597,15720.0,1.0,0,0
+216.63326653306615,15720.0,1.0,0,0
+217.03406813627254,15720.0,1.0,0,0
+217.43486973947896,15720.0,1.0,0,0
+217.83567134268537,15720.0,1.0,0,0
+218.2364729458918,15720.0,1.0,0,0
+218.6372745490982,15720.0,1.0,0,0
+219.0380761523046,15720.0,1.0,0,0
+219.43887775551104,15720.0,1.0,0,0
+219.83967935871743,15720.0,1.0,0,0
+220.24048096192385,15720.0,1.0,0,0
+220.64128256513027,15720.0,1.0,0,0
+221.04208416833666,15720.0,1.0,0,0
+221.4428857715431,15720.0,1.0,0,0
+221.8436873747495,15720.0,1.0,0,0
+222.2444889779559,15720.0,1.0,0,0
+222.64529058116233,15720.0,1.0,0,0
+223.04609218436875,15720.0,1.0,0,0
+223.44689378757516,15720.0,1.0,0,0
+223.84769539078155,15720.0,1.0,0,0
+224.248496993988,15720.0,1.0,0,0
+224.6492985971944,15720.0,1.0,0,0
+225.0501002004008,15720.0,1.0,0,0
+225.45090180360722,15720.0,1.0,0,0
+225.8517034068136,15720.0,1.0,0,0
+226.25250501002006,15720.0,1.0,0,0
+226.65330661322645,15720.0,1.0,0,0
+227.05410821643287,15720.0,1.0,0,0
+227.45490981963928,15720.0,1.0,0,0
+227.8557114228457,15720.0,1.0,0,0
+228.25651302605212,15720.0,1.0,0,0
+228.6573146292585,15720.0,1.0,0,0
+229.05811623246493,15720.0,1.0,0,0
+229.45891783567134,15720.0,1.0,0,0
+229.85971943887776,15720.0,1.0,0,0
+230.26052104208418,15720.0,1.0,0,0
+230.6613226452906,15720.0,1.0,0,0
+231.06212424849699,15720.0,1.0,0,0
+231.4629258517034,15720.0,1.0,0,0
+231.86372745490982,15720.0,1.0,0,0
+232.26452905811624,15720.0,1.0,0,0
+232.66533066132266,15720.0,1.0,0,0
+233.06613226452907,15720.0,1.0,0,0
+233.46693386773546,15720.0,1.0,0,0
+233.86773547094188,15720.0,1.0,0,0
+234.2685370741483,15720.0,1.0,0,0
+234.66933867735472,15720.0,1.0,0,0
+235.07014028056113,15720.0,1.0,0,0
+235.47094188376755,15720.0,1.0,0,0
+235.87174348697394,15720.0,1.0,0,0
+236.27254509018036,15720.0,1.0,0,0
+236.67334669338678,15720.0,1.0,0,0
+237.0741482965932,15720.0,1.0,0,0
+237.4749498997996,15720.0,1.0,0,0
+237.87575150300603,15720.0,1.0,0,0
+238.27655310621242,15720.0,1.0,0,0
+238.67735470941884,15720.0,1.0,0,0
+239.07815631262525,15720.0,1.0,0,0
+239.47895791583167,15720.0,1.0,0,0
+239.8797595190381,15720.0,1.0,0,0
+240.2805611222445,15720.0,1.0,0,0
+240.6813627254509,15720.0,1.0,0,0
+241.0821643286573,15720.0,1.0,0,0
+241.48296593186373,15720.0,1.0,0,0
+241.88376753507015,15720.0,1.0,0,0
+242.28456913827657,15720.0,1.0,0,0
+242.68537074148298,15720.0,1.0,0,0
+243.08617234468937,15720.0,1.0,0,0
+243.4869739478958,15720.0,1.0,0,0
+243.8877755511022,15720.0,1.0,0,0
+244.28857715430863,15720.0,1.0,0,0
+244.68937875751504,15720.0,1.0,0,0
+245.09018036072143,15720.0,1.0,0,0
+245.49098196392785,15720.0,1.0,0,0
+245.89178356713427,15720.0,1.0,0,0
+246.2925851703407,15720.0,1.0,0,0
+246.6933867735471,15720.0,1.0,0,0
+247.09418837675352,15720.0,1.0,0,0
+247.4949899799599,15720.0,1.0,0,0
+247.89579158316633,15720.0,1.0,0,0
+248.29659318637275,15720.0,1.0,0,0
+248.69739478957916,15720.0,1.0,0,0
+249.09819639278558,15720.0,1.0,0,0
+249.498997995992,15720.0,1.0,0,0
+249.8997995991984,15720.0,1.0,0,0
+250.3006012024048,15720.0,1.0,0,0
+250.70140280561122,15720.0,1.0,0,0
+251.10220440881764,15720.0,1.0,0,0
+251.50300601202406,15720.0,1.0,0,0
+251.90380761523048,15720.0,1.0,0,0
+252.30460921843687,15720.0,1.0,0,0
+252.70541082164328,15720.0,1.0,0,0
+253.1062124248497,15720.0,1.0,0,0
+253.50701402805612,15720.0,1.0,0,0
+253.90781563126254,15720.0,1.0,0,0
+254.30861723446895,15720.0,1.0,0,0
+254.70941883767534,15720.0,1.0,0,0
+255.11022044088176,15720.0,1.0,0,0
+255.51102204408818,15720.0,1.0,0,0
+255.9118236472946,15720.0,1.0,0,0
+256.312625250501,15720.0,1.0,0,0
+256.71342685370746,15720.0,1.0,0,0
+257.1142284569138,15720.0,1.0,0,0
+257.51503006012024,15720.0,1.0,0,0
+257.9158316633267,15720.0,1.0,0,0
+258.3166332665331,15720.0,1.0,0,0
+258.71743486973946,15720.0,1.0,0,0
+259.1182364729459,15720.0,1.0,0,0
+259.5190380761523,15720.0,1.0,0,0
+259.9198396793587,15720.0,1.0,0,0
+260.32064128256513,15720.0,1.0,0,0
+260.7214428857716,15720.0,1.0,0,0
+261.12224448897797,15720.0,1.0,0,0
+261.52304609218436,15720.0,1.0,0,0
+261.9238476953908,15720.0,1.0,0,0
+262.3246492985972,15720.0,1.0,0,0
+262.7254509018036,15720.0,1.0,0,0
+263.12625250501003,15720.0,1.0,0,0
+263.5270541082165,15720.0,1.0,0,0
+263.92785571142286,15720.0,1.0,0,0
+264.32865731462925,15720.0,1.0,0,0
+264.7294589178357,15720.0,1.0,0,0
+265.1302605210421,15720.0,1.0,0,0
+265.5310621242485,15720.0,1.0,0,0
+265.9318637274549,15720.0,1.0,0,0
+266.3326653306613,15720.0,1.0,0,0
+266.7334669338677,15720.0,1.0,0,0
+267.13426853707415,15720.0,1.0,0,0
+267.5350701402806,15720.0,1.0,0,0
+267.935871743487,15720.0,1.0,0,0
+268.3366733466934,15720.0,1.0,0,0
+268.7374749498998,15720.0,1.0,0,0
+269.1382765531062,15720.0,1.0,0,0
+269.5390781563126,15720.0,1.0,0,0
+269.93987975951904,15720.0,1.0,0,0
+270.3406813627255,15720.0,1.0,0,0
+270.7414829659319,15720.0,1.0,0,0
+271.14228456913827,15720.0,1.0,0,0
+271.5430861723447,15720.0,1.0,0,0
+271.9438877755511,15720.0,1.0,0,0
+272.3446893787575,15720.0,1.0,0,0
+272.74549098196394,15720.0,1.0,0,0
+273.1462925851704,15720.0,1.0,0,0
+273.5470941883767,15720.0,1.0,0,0
+273.94789579158316,15720.0,1.0,0,0
+274.3486973947896,15720.0,1.0,0,0
+274.749498997996,15720.0,1.0,0,0
+275.1503006012024,15720.0,1.0,0,0
+275.55110220440883,15720.0,1.0,0,0
+275.9519038076152,15720.0,1.0,0,0
+276.3527054108216,15720.0,1.0,0,0
+276.75350701402806,15720.0,1.0,0,0
+277.1543086172345,15720.0,1.0,0,0
+277.5551102204409,15720.0,1.0,0,0
+277.9559118236473,15720.0,1.0,0,0
+278.35671342685373,15720.0,1.0,0,0
+278.7575150300601,15720.0,1.0,0,0
+279.1583166332665,15720.0,1.0,0,0
+279.55911823647295,15720.0,1.0,0,0
+279.9599198396794,15720.0,1.0,0,0
+280.3607214428858,15720.0,1.0,0,0
+280.7615230460922,15720.0,1.0,0,0
+281.1623246492986,15720.0,1.0,0,0
+281.563126252505,15720.0,1.0,0,0
+281.9639278557114,15720.0,1.0,0,0
+282.36472945891785,15720.0,1.0,0,0
+282.7655310621243,15720.0,1.0,0,0
+283.16633266533063,15720.0,1.0,0,0
+283.5671342685371,15720.0,1.0,0,0
+283.9679358717435,15720.0,1.0,0,0
+284.3687374749499,15720.0,1.0,0,0
+284.7695390781563,15720.0,1.0,0,0
+285.17034068136275,15720.0,1.0,0,0
+285.57114228456913,15720.0,1.0,0,0
+285.9719438877755,15720.0,1.0,0,0
+286.37274549098197,15720.0,1.0,0,0
+286.7735470941884,15720.0,1.0,0,0
+287.1743486973948,15720.0,1.0,0,0
+287.5751503006012,15720.0,1.0,0,0
+287.97595190380764,15720.0,1.0,0,0
+288.37675350701403,15720.0,1.0,0,0
+288.7775551102204,15720.0,1.0,0,0
+289.17835671342687,15720.0,1.0,0,0
+289.5791583166333,15720.0,1.0,0,0
+289.97995991983964,15720.0,1.0,0,0
+290.3807615230461,15720.0,1.0,0,0
+290.78156312625254,15720.0,1.0,0,0
+291.1823647294589,15720.0,1.0,0,0
+291.5831663326653,15720.0,1.0,0,0
+291.98396793587176,15720.0,1.0,0,0
+292.38476953907815,15720.0,1.0,0,0
+292.78557114228454,15720.0,1.0,0,0
+293.186372745491,15720.0,1.0,0,0
+293.58717434869743,15720.0,1.0,0,0
+293.9879759519038,15720.0,1.0,0,0
+294.3887775551102,15720.0,1.0,0,0
+294.78957915831666,15720.0,1.0,0,0
+295.19038076152304,15720.0,1.0,0,0
+295.59118236472943,15720.0,1.0,0,0
+295.9919839679359,15720.0,1.0,0,0
+296.3927855711423,15720.0,1.0,0,0
+296.7935871743487,15720.0,1.0,0,0
+297.1943887775551,15720.0,1.0,0,0
+297.59519038076155,15720.0,1.0,0,0
+297.99599198396794,15720.0,1.0,0,0
+298.39679358717433,15720.0,1.0,0,0
+298.7975951903808,15720.0,1.0,0,0
+299.1983967935872,15720.0,1.0,0,0
+299.59919839679355,15720.0,1.0,0,0
+300.0,15720.0,1.0,0,0
diff --git a/pareto_knee_analysis.png b/pareto_knee_analysis.png
new file mode 100644
index 0000000..364785d
Binary files /dev/null and b/pareto_knee_analysis.png differ
diff --git a/pareto_optimization.py b/pareto_optimization.py
new file mode 100644
index 0000000..8aa1de9
--- /dev/null
+++ b/pareto_optimization.py
@@ -0,0 +1,1027 @@
+"""
+Moon Colony Logistics - Pareto Front & Knee Point Analysis
+
+Multi-objective optimization for Space Elevator + Rocket combination:
+- Objective 1: Minimize completion time T
+- Objective 2: Minimize total energy E
+
+Finds the Pareto front and identifies the "knee point" as the optimal trade-off.
+"""
+
+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, Optional
+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
+ELEVATOR_SPECIFIC_ENERGY = 157.2e9 # J per metric ton (157.2 MJ/kg * 1000)
+
+# 火箭参数
+PAYLOAD_PER_LAUNCH = 125 # metric tons per launch
+ISP = 450
+SPECIFIC_FUEL_ENERGY = 15.5e6 # J/kg
+ALPHA = 0.10
+NUM_STAGES = 3
+DELTA_V_BASE = 13300 # m/s
+
+
+# ============== 发射场定义 ==============
+@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 delta_v_loss(self) -> float:
+ v_equator = OMEGA_EARTH * R_EARTH
+ v_site = OMEGA_EARTH * R_EARTH * np.cos(np.radians(self.abs_latitude))
+ return v_equator - v_site
+
+ @property
+ def total_delta_v(self) -> float:
+ return DELTA_V_BASE + self.delta_v_loss
+
+
+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 calculate_scenario(completion_years: float) -> Optional[Dict]:
+ """
+ 计算给定完成年限下的最优方案(电梯优先+低纬火箭)
+
+ 返回: 方案详情字典,如果无法完成则返回None
+ """
+ # 太空电梯运输量
+ 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 {
+ 'years': completion_years,
+ 'elevator_payload': elevator_payload,
+ 'rocket_payload': 0,
+ 'elevator_energy_PJ': elevator_energy / 1e15,
+ 'rocket_energy_PJ': 0,
+ 'total_energy_PJ': elevator_energy / 1e15,
+ 'rocket_launches': 0,
+ 'sites_used': 0,
+ 'elevator_fraction': 1.0,
+ }
+
+ # 需要火箭发射
+ rocket_launches_needed = int(np.ceil(remaining_payload / PAYLOAD_PER_LAUNCH))
+
+ # 按纬度优先分配火箭发射
+ days_available = completion_years * 365
+ max_launches_per_site = int(days_available)
+
+ # 检查是否能在规定时间内完成
+ total_rocket_capacity = len(LAUNCH_SITES) * max_launches_per_site * PAYLOAD_PER_LAUNCH
+ if remaining_payload > total_rocket_capacity:
+ return None # 无法完成
+
+ rocket_energy = 0
+ sites_used = 0
+ remaining_launches = rocket_launches_needed
+
+ for site in LAUNCH_SITES:
+ if remaining_launches <= 0:
+ break
+ allocated = min(remaining_launches, max_launches_per_site)
+ rocket_energy += rocket_energy_per_ton(site) * PAYLOAD_PER_LAUNCH * allocated
+ remaining_launches -= allocated
+ if allocated > 0:
+ sites_used += 1
+
+ rocket_payload = rocket_launches_needed * PAYLOAD_PER_LAUNCH
+ total_energy = elevator_energy + rocket_energy
+
+ return {
+ 'years': completion_years,
+ 'elevator_payload': elevator_payload,
+ 'rocket_payload': rocket_payload,
+ 'elevator_energy_PJ': elevator_energy / 1e15,
+ '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,
+ }
+
+
+# ============== Pareto 前沿计算 ==============
+
+def generate_pareto_front(
+ year_min: float = 100,
+ year_max: float = 300,
+ num_points: int = 500
+) -> pd.DataFrame:
+ """
+ 生成 Pareto 前沿数据
+
+ 对于此问题,由于"时间越长→能量越低"是单调关系,
+ 所有可行解都在Pareto前沿上(这是一个双目标单调权衡问题)。
+ """
+ years_range = np.linspace(year_min, year_max, num_points)
+
+ results = []
+ for years in years_range:
+ scenario = calculate_scenario(years)
+ if scenario is not None:
+ results.append({
+ 'years': years,
+ 'energy_PJ': scenario['total_energy_PJ'],
+ 'elevator_fraction': scenario['elevator_fraction'],
+ 'sites_used': scenario['sites_used'],
+ 'rocket_launches': scenario['rocket_launches'],
+ })
+
+ return pd.DataFrame(results)
+
+
+# ============== 膝点检测算法 ==============
+
+def find_knee_point_max_curvature(df: pd.DataFrame) -> int:
+ """
+ 方法1: 最大曲率法
+
+ 在归一化后的 (T, E) 曲线上找曲率最大的点。
+ 曲率 κ = |y''| / (1 + y'^2)^(3/2)
+ """
+ # 归一化
+ T = df['years'].values
+ E = df['energy_PJ'].values
+
+ T_norm = (T - T.min()) / (T.max() - T.min())
+ E_norm = (E - E.min()) / (E.max() - E.min())
+
+ # 计算一阶和二阶导数(使用有限差分)
+ dE = np.gradient(E_norm, T_norm)
+ d2E = np.gradient(dE, T_norm)
+
+ # 计算曲率
+ curvature = np.abs(d2E) / np.power(1 + dE**2, 1.5)
+
+ # 排除边界点
+ curvature[:5] = 0
+ curvature[-5:] = 0
+
+ knee_idx = np.argmax(curvature)
+ return knee_idx
+
+
+def find_knee_point_max_distance(df: pd.DataFrame) -> int:
+ """
+ 方法2: 最大距离法(L-method / Kneedle思想)
+
+ 找到距离"起点-终点连线"最远的点。
+ 这通常对应于"边际收益递减"最明显的拐点。
+ """
+ T = df['years'].values
+ E = df['energy_PJ'].values
+
+ # 归一化到 [0, 1]
+ T_norm = (T - T.min()) / (T.max() - T.min())
+ E_norm = (E - E.min()) / (E.max() - E.min())
+
+ # 起点和终点
+ p1 = np.array([T_norm[0], E_norm[0]])
+ p2 = np.array([T_norm[-1], E_norm[-1]])
+
+ # 计算每个点到直线 p1-p2 的距离
+ line_vec = p2 - p1
+ line_len = np.linalg.norm(line_vec)
+ line_unit = line_vec / line_len
+
+ distances = []
+ for i in range(len(T)):
+ point = np.array([T_norm[i], E_norm[i]])
+ point_vec = point - p1
+ # 投影长度
+ proj_len = np.dot(point_vec, line_unit)
+ # 垂直距离
+ proj_point = p1 + proj_len * line_unit
+ dist = np.linalg.norm(point - proj_point)
+ distances.append(dist)
+
+ distances = np.array(distances)
+ knee_idx = np.argmax(distances)
+ return knee_idx
+
+
+def find_knee_point_marginal(df: pd.DataFrame, threshold: float = 0.1) -> int:
+ """
+ 方法3: 边际收益法
+
+ 找到"多投入1年时间,能量下降比例"开始低于阈值的点。
+ 即:|dE/dT| / |E| < threshold 的第一个点
+ """
+ T = df['years'].values
+ E = df['energy_PJ'].values
+
+ # 计算边际能量变化率
+ dE_dT = np.gradient(E, T)
+
+ # 相对变化率
+ marginal_ratio = np.abs(dE_dT) / E
+
+ # 找到第一个低于阈值的点(跳过前面几个点)
+ for i in range(10, len(marginal_ratio)):
+ if marginal_ratio[i] < threshold:
+ return i
+
+ return len(df) // 2 # 默认返回中点
+
+
+def find_knee_point_elbow(df: pd.DataFrame) -> int:
+ """
+ 方法4: 肘部法则(Elbow Method)
+
+ 类似K-means的肘部法则,找到曲线"弯曲"最明显的点。
+ 使用二阶导数的符号变化或绝对值。
+ """
+ T = df['years'].values
+ E = df['energy_PJ'].values
+
+ # 归一化
+ T_norm = (T - T.min()) / (T.max() - T.min() + 1e-10)
+ E_norm = (E - E.min()) / (E.max() - E.min() + 1e-10)
+
+ # 计算二阶差分
+ d2E = np.diff(np.diff(E_norm))
+
+ # 找到绝对值最大的点
+ knee_idx = np.argmax(np.abs(d2E)) + 1
+ return min(knee_idx, len(df) - 1)
+
+
+# ============== 综合膝点分析 ==============
+
+def analyze_knee_points(df: pd.DataFrame) -> Dict:
+ """
+ 使用多种方法找膝点,并综合给出推荐
+ """
+ methods = {
+ 'max_curvature': find_knee_point_max_curvature(df),
+ 'max_distance': find_knee_point_max_distance(df),
+ 'elbow': find_knee_point_elbow(df),
+ }
+
+ # 每种方法对应的年份和能量
+ results = {}
+ for method, idx in methods.items():
+ results[method] = {
+ 'index': idx,
+ 'years': df.iloc[idx]['years'],
+ 'energy_PJ': df.iloc[idx]['energy_PJ'],
+ 'elevator_fraction': df.iloc[idx]['elevator_fraction'],
+ 'sites_used': df.iloc[idx]['sites_used'],
+ }
+
+ # 推荐:使用最大距离法(最稳健)
+ recommended = results['max_distance']
+
+ return {
+ 'methods': results,
+ 'recommended': recommended,
+ }
+
+
+# ============== 可视化 ==============
+
+def plot_pareto_analysis(
+ df: pd.DataFrame,
+ knee_analysis: Dict,
+ save_path: str = '/Volumes/Files/code/mm/20260130_b/pareto_knee_analysis.png'
+):
+ """
+ 绘制 Pareto 前沿与膝点分析图
+ """
+ fig, axes = plt.subplots(2, 2, figsize=(16, 14))
+
+ # ========== 图1: Pareto 前沿(时间 vs 能量) ==========
+ ax1 = axes[0, 0]
+
+ ax1.plot(df['years'], df['energy_PJ'], 'b-', linewidth=2, label='Pareto Front')
+ ax1.fill_between(df['years'], df['energy_PJ'], alpha=0.2)
+
+ # 标记各方法找到的膝点
+ colors = {'max_curvature': 'red', 'max_distance': 'green', 'elbow': 'orange'}
+ markers = {'max_curvature': 'o', 'max_distance': 's', 'elbow': '^'}
+
+ for method, data in knee_analysis['methods'].items():
+ ax1.scatter(data['years'], data['energy_PJ'],
+ c=colors[method], marker=markers[method], s=150, zorder=5,
+ label=f'{method}: {data["years"]:.0f}y, {data["energy_PJ"]:.0f}PJ')
+
+ # 标记推荐点
+ rec = knee_analysis['recommended']
+ ax1.axvline(x=rec['years'], color='green', linestyle='--', alpha=0.7)
+ ax1.axhline(y=rec['energy_PJ'], color='green', linestyle='--', alpha=0.7)
+
+ ax1.set_xlabel('Completion Time (years)', fontsize=12)
+ ax1.set_ylabel('Total Energy (PJ)', fontsize=12)
+ ax1.set_title('Pareto Front: Time vs Energy Trade-off\n(Knee Points by Different Methods)', fontsize=13)
+ ax1.legend(loc='upper right', fontsize=9)
+ ax1.grid(True, alpha=0.3)
+
+ # ========== 图2: 归一化 Pareto 前沿 + 距离可视化 ==========
+ ax2 = axes[0, 1]
+
+ T = df['years'].values
+ E = df['energy_PJ'].values
+ T_norm = (T - T.min()) / (T.max() - T.min())
+ E_norm = (E - E.min()) / (E.max() - E.min())
+
+ ax2.plot(T_norm, E_norm, 'b-', linewidth=2, label='Normalized Pareto Front')
+
+ # 画起点-终点连线
+ ax2.plot([T_norm[0], T_norm[-1]], [E_norm[0], E_norm[-1]],
+ 'r--', linewidth=1.5, label='Baseline (Start-End)')
+
+ # 标记最大距离点
+ knee_idx = knee_analysis['methods']['max_distance']['index']
+ ax2.scatter(T_norm[knee_idx], E_norm[knee_idx],
+ c='green', marker='s', s=200, zorder=5, label='Knee Point (Max Distance)')
+
+ # 画垂直距离线
+ p1 = np.array([T_norm[0], E_norm[0]])
+ p2 = np.array([T_norm[-1], E_norm[-1]])
+ line_vec = p2 - p1
+ line_unit = line_vec / np.linalg.norm(line_vec)
+ point = np.array([T_norm[knee_idx], E_norm[knee_idx]])
+ proj_len = np.dot(point - p1, line_unit)
+ proj_point = p1 + proj_len * line_unit
+ ax2.plot([T_norm[knee_idx], proj_point[0]], [E_norm[knee_idx], proj_point[1]],
+ 'g-', linewidth=2, label='Max Distance')
+
+ ax2.set_xlabel('Normalized Time', fontsize=12)
+ ax2.set_ylabel('Normalized Energy', fontsize=12)
+ ax2.set_title('Knee Point Detection\n(Maximum Distance to Baseline)', fontsize=13)
+ ax2.legend(fontsize=9)
+ ax2.grid(True, alpha=0.3)
+ ax2.set_xlim(-0.05, 1.05)
+ ax2.set_ylim(-0.05, 1.05)
+
+ # ========== 图3: 边际收益分析 ==========
+ ax3 = axes[1, 0]
+
+ # 计算边际能量变化
+ dE_dT = np.gradient(E, T)
+ marginal_savings = -dE_dT # 每多1年节省的能量
+
+ ax3.plot(T, marginal_savings, 'purple', linewidth=2)
+ ax3.fill_between(T, marginal_savings, alpha=0.2, color='purple')
+
+ # 标记推荐点
+ ax3.axvline(x=rec['years'], color='green', linestyle='--', linewidth=2,
+ label=f'Recommended: {rec["years"]:.0f} years')
+
+ ax3.set_xlabel('Completion Time (years)', fontsize=12)
+ ax3.set_ylabel('Marginal Energy Saving (PJ/year)', fontsize=12)
+ ax3.set_title('Marginal Benefit Analysis\n(Energy Saved per Additional Year)', fontsize=13)
+ ax3.legend()
+ ax3.grid(True, alpha=0.3)
+
+ # ========== 图4: 方案构成随时间变化 ==========
+ ax4 = axes[1, 1]
+
+ ax4.fill_between(df['years'], 0, df['elevator_fraction'] * 100,
+ alpha=0.7, color='green', label='Space Elevator')
+ ax4.fill_between(df['years'], df['elevator_fraction'] * 100, 100,
+ alpha=0.7, color='red', label='Rockets')
+
+ # 标记推荐点
+ ax4.axvline(x=rec['years'], color='black', linestyle='--', linewidth=2,
+ label=f'Recommended: {rec["years"]:.0f}y ({rec["elevator_fraction"]*100:.0f}% elevator)')
+
+ ax4.set_xlabel('Completion Time (years)', fontsize=12)
+ ax4.set_ylabel('Payload Share (%)', fontsize=12)
+ ax4.set_title('Payload Distribution at Different Timelines', fontsize=13)
+ ax4.legend(loc='center right')
+ ax4.set_ylim(0, 100)
+ ax4.grid(True, alpha=0.3)
+
+ plt.suptitle('Pareto Optimization: Space Elevator + Rocket Combination\n'
+ f'Recommended Knee Point: {rec["years"]:.0f} years, {rec["energy_PJ"]:.0f} PJ',
+ fontsize=15, y=1.02)
+
+ plt.tight_layout()
+ plt.savefig(save_path, dpi=150, bbox_inches='tight')
+ print(f"Pareto分析图已保存至: {save_path}")
+
+ return fig
+
+
+def plot_decision_recommendation(
+ df: pd.DataFrame,
+ knee_analysis: Dict,
+ save_path: str = '/Volumes/Files/code/mm/20260130_b/decision_recommendation.png'
+):
+ """
+ 绘制决策建议图(更适合放在报告中)
+ """
+ rec = knee_analysis['recommended']
+
+ fig, ax = plt.subplots(figsize=(12, 8))
+
+ # 主曲线
+ ax.plot(df['years'], df['energy_PJ'], 'b-', linewidth=3, label='Energy-Time Trade-off')
+
+ # 填充区域
+ ax.fill_between(df['years'], df['energy_PJ'], alpha=0.15, color='blue')
+
+ # 标记推荐点
+ ax.scatter(rec['years'], rec['energy_PJ'], c='red', s=300, zorder=5,
+ marker='*', edgecolors='black', linewidth=2)
+
+ # 标注推荐点
+ ax.annotate(f"RECOMMENDED\n{rec['years']:.0f} years\n{rec['energy_PJ']:.0f} PJ\n"
+ f"({rec['elevator_fraction']*100:.0f}% Elevator)",
+ xy=(rec['years'], rec['energy_PJ']),
+ xytext=(rec['years'] + 30, rec['energy_PJ'] + 3000),
+ fontsize=12, fontweight='bold',
+ bbox=dict(boxstyle='round,pad=0.5', facecolor='yellow', alpha=0.8),
+ arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.2',
+ color='black', lw=2))
+
+ # 标记几个关键时间点
+ key_points = [
+ (100, 'Minimum\nfeasible'),
+ (186, 'Elevator-only\nfeasible'),
+ (219, 'Rocket-only\nfeasible'),
+ ]
+
+ for year, label in key_points:
+ if year >= df['years'].min() and year <= df['years'].max():
+ idx = np.argmin(np.abs(df['years'] - year))
+ energy = df.iloc[idx]['energy_PJ']
+ ax.axvline(x=year, color='gray', linestyle=':', alpha=0.7)
+ ax.text(year, ax.get_ylim()[1] * 0.95, label, ha='center', fontsize=9,
+ bbox=dict(facecolor='white', alpha=0.7))
+
+ ax.set_xlabel('Completion Time (years)', fontsize=14)
+ ax.set_ylabel('Total Energy Consumption (PJ)', fontsize=14)
+ ax.set_title('Optimal Decision Point for Moon Colony Construction\n'
+ '(100 Million Metric Tons Payload)', fontsize=16)
+ ax.grid(True, alpha=0.3)
+
+ # 添加文字说明
+ textstr = (f"Knee Point Analysis Result:\n"
+ f"• Optimal Timeline: {rec['years']:.0f} years\n"
+ f"• Total Energy: {rec['energy_PJ']:.0f} PJ\n"
+ f"• Elevator Share: {rec['elevator_fraction']*100:.1f}%\n"
+ f"• Rocket Sites Used: {rec['sites_used']}")
+
+ props = dict(boxstyle='round', facecolor='lightgreen', alpha=0.8)
+ ax.text(0.98, 0.55, textstr, transform=ax.transAxes, fontsize=11,
+ verticalalignment='top', horizontalalignment='right', bbox=props)
+
+ plt.tight_layout()
+ plt.savefig(save_path, dpi=150, bbox_inches='tight')
+ print(f"决策建议图已保存至: {save_path}")
+
+ return fig
+
+
+def print_analysis_report(df: pd.DataFrame, knee_analysis: Dict):
+ """
+ 打印详细分析报告
+ """
+ print("\n" + "=" * 80)
+ print("PARETO FRONT & KNEE POINT ANALYSIS REPORT")
+ print("Moon Colony Logistics Optimization")
+ print("=" * 80)
+
+ print(f"\n1. PROBLEM DEFINITION")
+ print(f" - Total payload: {TOTAL_PAYLOAD/1e6:.0f} million metric tons")
+ print(f" - Elevator capacity: {TOTAL_ELEVATOR_CAPACITY:,} tons/year")
+ print(f" - Rocket capacity: {len(LAUNCH_SITES) * 365 * PAYLOAD_PER_LAUNCH:,} tons/year")
+
+ print(f"\n2. PARETO FRONT RANGE")
+ print(f" - Minimum feasible time: {df['years'].min():.1f} years")
+ print(f" - Analysis range: {df['years'].min():.0f} - {df['years'].max():.0f} years")
+ print(f" - Energy range: {df['energy_PJ'].min():.0f} - {df['energy_PJ'].max():.0f} PJ")
+
+ print(f"\n3. KNEE POINT DETECTION (Multiple Methods)")
+ print("-" * 60)
+ print(f" {'Method':<20} {'Years':>10} {'Energy (PJ)':>15} {'Elev. %':>10}")
+ print("-" * 60)
+
+ for method, data in knee_analysis['methods'].items():
+ print(f" {method:<20} {data['years']:>10.0f} {data['energy_PJ']:>15.0f} "
+ f"{data['elevator_fraction']*100:>9.1f}%")
+
+ print("-" * 60)
+
+ rec = knee_analysis['recommended']
+ print(f"\n4. RECOMMENDED OPTIMAL POINT (Max Distance Method)")
+ print(f" ┌─────────────────────────────────────────────┐")
+ print(f" │ Completion Time: {rec['years']:>10.0f} years │")
+ print(f" │ Total Energy: {rec['energy_PJ']:>10.0f} PJ │")
+ print(f" │ Elevator Share: {rec['elevator_fraction']*100:>10.1f} % │")
+ print(f" │ Rocket Sites Used: {rec['sites_used']:>10.0f} │")
+ print(f" └─────────────────────────────────────────────┘")
+
+ print(f"\n5. INTERPRETATION")
+ print(f" At the recommended {rec['years']:.0f}-year timeline:")
+
+ # 计算方案详情
+ scenario = calculate_scenario(rec['years'])
+ print(f" - Space Elevator delivers: {scenario['elevator_payload']/1e6:.1f}M tons")
+ print(f" - Rockets deliver: {scenario['rocket_payload']/1e6:.1f}M tons")
+ print(f" - Total rocket launches: {scenario['rocket_launches']:,}")
+
+ # 与极端方案对比
+ min_time_scenario = calculate_scenario(df['years'].min() + 1)
+ max_time_idx = len(df) - 1
+
+ print(f"\n Compared to minimum-time ({df['years'].min():.0f}y):")
+ if min_time_scenario:
+ time_diff = rec['years'] - df['years'].min()
+ energy_diff = min_time_scenario['total_energy_PJ'] - rec['energy_PJ']
+ print(f" - Extra time: +{time_diff:.0f} years")
+ print(f" - Energy saved: {energy_diff:.0f} PJ ({energy_diff/min_time_scenario['total_energy_PJ']*100:.1f}%)")
+
+ print(f"\n Compared to elevator-only (~186y):")
+ elevator_only_energy = TOTAL_PAYLOAD * ELEVATOR_SPECIFIC_ENERGY / 1e15
+ energy_overhead = rec['energy_PJ'] - elevator_only_energy
+ time_saved = 186 - rec['years']
+ print(f" - Time saved: {time_saved:.0f} years")
+ print(f" - Extra energy: {energy_overhead:.0f} PJ ({energy_overhead/elevator_only_energy*100:.1f}%)")
+
+ print("\n" + "=" * 80)
+ print("CONCLUSION: The knee point represents the optimal trade-off where")
+ print("extending the timeline further yields diminishing energy savings.")
+ print("=" * 80)
+
+
+# ============== 组合方案范围内的膝点分析 ==============
+
+def analyze_combined_range():
+ """
+ 在组合方案有效范围内(101-186年)进行膝点分析
+ 186年后电梯可独立完成,曲线平坦,无意义
+ """
+ # 计算电梯独立完成的时间
+ elevator_only_years = TOTAL_PAYLOAD / TOTAL_ELEVATOR_CAPACITY
+ print(f"电梯独立完成需要: {elevator_only_years:.1f} 年")
+
+ # 在组合方案范围内生成Pareto前沿
+ year_min = 101
+ year_max = elevator_only_years # ~186年
+
+ df = generate_pareto_front(year_min=year_min, year_max=year_max, num_points=300)
+
+ return df, year_min, year_max
+
+
+def plot_combined_range_analysis(
+ df: pd.DataFrame,
+ knee_analysis: Dict,
+ year_range: Tuple[float, float],
+ save_path: str = '/Volumes/Files/code/mm/20260130_b/pareto_combined_range.png'
+):
+ """
+ 绘制组合方案范围内的Pareto分析图
+ """
+ fig, axes = plt.subplots(2, 2, figsize=(16, 14))
+
+ year_min, year_max = year_range
+
+ # ========== 图1: Pareto 前沿(时间 vs 能量) ==========
+ ax1 = axes[0, 0]
+
+ ax1.plot(df['years'], df['energy_PJ'], 'b-', linewidth=2.5, label='Pareto Front')
+ ax1.fill_between(df['years'], df['energy_PJ'], alpha=0.2)
+
+ # 标记各方法找到的膝点
+ colors = {'max_curvature': 'red', 'max_distance': 'green', 'elbow': 'orange'}
+ markers = {'max_curvature': 'o', 'max_distance': 's', 'elbow': '^'}
+ labels = {'max_curvature': 'Max Curvature', 'max_distance': 'Max Distance', 'elbow': 'Elbow'}
+
+ for method, data in knee_analysis['methods'].items():
+ ax1.scatter(data['years'], data['energy_PJ'],
+ c=colors[method], marker=markers[method], s=200, zorder=5,
+ edgecolors='black', linewidth=1.5,
+ label=f'{labels[method]}: {data["years"]:.0f}y, {data["energy_PJ"]:.0f}PJ')
+
+ # 标记推荐点
+ rec = knee_analysis['recommended']
+ ax1.axvline(x=rec['years'], color='green', linestyle='--', alpha=0.7, linewidth=2)
+ ax1.axhline(y=rec['energy_PJ'], color='green', linestyle='--', alpha=0.7, linewidth=2)
+
+ ax1.set_xlabel('Completion Time (years)', fontsize=12)
+ ax1.set_ylabel('Total Energy (PJ)', fontsize=12)
+ ax1.set_title(f'Pareto Front: Combined Scenario ({year_min:.0f}-{year_max:.0f} years)\n'
+ f'Knee Points by Different Methods', fontsize=13)
+ ax1.legend(loc='upper right', fontsize=10)
+ ax1.grid(True, alpha=0.3)
+
+ # ========== 图2: 归一化 + 距离可视化 ==========
+ ax2 = axes[0, 1]
+
+ T = df['years'].values
+ E = df['energy_PJ'].values
+ T_norm = (T - T.min()) / (T.max() - T.min())
+ E_norm = (E - E.min()) / (E.max() - E.min())
+
+ ax2.plot(T_norm, E_norm, 'b-', linewidth=2.5, label='Normalized Pareto Front')
+
+ # 画起点-终点连线
+ ax2.plot([T_norm[0], T_norm[-1]], [E_norm[0], E_norm[-1]],
+ 'r--', linewidth=2, label='Baseline (Start→End)')
+
+ # 标记最大距离点
+ knee_idx = knee_analysis['methods']['max_distance']['index']
+ ax2.scatter(T_norm[knee_idx], E_norm[knee_idx],
+ c='green', marker='s', s=250, zorder=5, edgecolors='black', linewidth=2,
+ label='Knee Point')
+
+ # 画垂直距离线
+ p1 = np.array([T_norm[0], E_norm[0]])
+ p2 = np.array([T_norm[-1], E_norm[-1]])
+ line_vec = p2 - p1
+ line_unit = line_vec / np.linalg.norm(line_vec)
+ point = np.array([T_norm[knee_idx], E_norm[knee_idx]])
+ proj_len = np.dot(point - p1, line_unit)
+ proj_point = p1 + proj_len * line_unit
+ ax2.plot([T_norm[knee_idx], proj_point[0]], [E_norm[knee_idx], proj_point[1]],
+ 'g-', linewidth=3, label=f'Max Distance')
+
+ ax2.set_xlabel('Normalized Time (0=min, 1=max)', fontsize=12)
+ ax2.set_ylabel('Normalized Energy (0=min, 1=max)', fontsize=12)
+ ax2.set_title('Knee Point Detection\n(Maximum Distance to Baseline)', fontsize=13)
+ ax2.legend(fontsize=10)
+ ax2.grid(True, alpha=0.3)
+ ax2.set_xlim(-0.05, 1.05)
+ ax2.set_ylim(-0.05, 1.05)
+
+ # ========== 图3: 曲率分析 ==========
+ ax3 = axes[1, 0]
+
+ # 计算曲率
+ dE = np.gradient(E_norm, T_norm)
+ d2E = np.gradient(dE, T_norm)
+ curvature = np.abs(d2E) / np.power(1 + dE**2, 1.5)
+
+ ax3.plot(T, curvature, 'purple', linewidth=2)
+ ax3.fill_between(T, curvature, alpha=0.2, color='purple')
+
+ # 标记最大曲率点
+ curv_idx = knee_analysis['methods']['max_curvature']['index']
+ ax3.scatter(T[curv_idx], curvature[curv_idx], c='red', s=200, zorder=5,
+ marker='o', edgecolors='black', linewidth=2,
+ label=f'Max Curvature: {T[curv_idx]:.0f} years')
+
+ ax3.set_xlabel('Completion Time (years)', fontsize=12)
+ ax3.set_ylabel('Curvature κ', fontsize=12)
+ ax3.set_title('Curvature Analysis\n(Higher curvature = sharper bend)', fontsize=13)
+ ax3.legend(fontsize=10)
+ ax3.grid(True, alpha=0.3)
+
+ # ========== 图4: 边际收益 + 方案构成 ==========
+ ax4 = axes[1, 1]
+
+ # 边际节省
+ dE_dT = np.gradient(E, T)
+ marginal_savings = -dE_dT # 每多1年节省的能量
+
+ ax4_twin = ax4.twinx()
+
+ # 边际收益曲线
+ line1, = ax4.plot(T, marginal_savings, 'b-', linewidth=2, label='Marginal Saving (PJ/year)')
+ ax4.fill_between(T, marginal_savings, alpha=0.15, color='blue')
+
+ # 电梯占比
+ line2, = ax4_twin.plot(df['years'], df['elevator_fraction'] * 100, 'g--', linewidth=2,
+ label='Elevator Share (%)')
+
+ # 标记推荐点
+ ax4.axvline(x=rec['years'], color='red', linestyle=':', linewidth=2,
+ label=f'Recommended: {rec["years"]:.0f}y')
+
+ ax4.set_xlabel('Completion Time (years)', fontsize=12)
+ ax4.set_ylabel('Marginal Energy Saving (PJ/year)', fontsize=12, color='blue')
+ ax4_twin.set_ylabel('Elevator Share (%)', fontsize=12, color='green')
+ ax4.set_title('Marginal Benefit & Payload Distribution', fontsize=13)
+
+ # 合并图例
+ lines = [line1, line2]
+ labels = [l.get_label() for l in lines]
+ ax4.legend(lines, labels, loc='upper right', fontsize=10)
+ ax4.grid(True, alpha=0.3)
+
+ plt.suptitle(f'Pareto Optimization: Combined Scenario (Elevator + Rockets)\n'
+ f'Analysis Range: {year_min:.0f} - {year_max:.0f} years | '
+ f'Recommended: {rec["years"]:.0f} years, {rec["energy_PJ"]:.0f} PJ',
+ fontsize=15, y=1.02)
+
+ plt.tight_layout()
+ plt.savefig(save_path, dpi=150, bbox_inches='tight')
+ print(f"组合方案Pareto分析图已保存至: {save_path}")
+
+ return fig
+
+
+def plot_combined_decision(
+ df: pd.DataFrame,
+ knee_analysis: Dict,
+ year_range: Tuple[float, float],
+ save_path: str = '/Volumes/Files/code/mm/20260130_b/combined_decision.png'
+):
+ """
+ 绘制组合方案决策图
+ """
+ rec = knee_analysis['recommended']
+ year_min, year_max = year_range
+
+ fig, ax = plt.subplots(figsize=(14, 9))
+
+ T = df['years'].values
+ E = df['energy_PJ'].values
+
+ # 主曲线
+ ax.plot(T, E, 'b-', linewidth=3, label='Energy-Time Trade-off')
+ ax.fill_between(T, E, alpha=0.15, color='blue')
+
+ # 标记三种方法的膝点
+ methods_info = knee_analysis['methods']
+
+ # 最大距离法(推荐)
+ ax.scatter(methods_info['max_distance']['years'], methods_info['max_distance']['energy_PJ'],
+ c='green', s=400, zorder=5, marker='*', edgecolors='black', linewidth=2)
+
+ # 最大曲率法
+ ax.scatter(methods_info['max_curvature']['years'], methods_info['max_curvature']['energy_PJ'],
+ c='red', s=200, zorder=5, marker='o', edgecolors='black', linewidth=1.5)
+
+ # 肘部法
+ ax.scatter(methods_info['elbow']['years'], methods_info['elbow']['energy_PJ'],
+ c='orange', s=200, zorder=5, marker='^', edgecolors='black', linewidth=1.5)
+
+ # 标注推荐点
+ ax.annotate(f"★ RECOMMENDED (Max Distance)\n"
+ f"{rec['years']:.0f} years | {rec['energy_PJ']:.0f} PJ\n"
+ f"Elevator: {rec['elevator_fraction']*100:.0f}%",
+ xy=(rec['years'], rec['energy_PJ']),
+ xytext=(rec['years'] + 15, rec['energy_PJ'] + 2500),
+ fontsize=12, fontweight='bold',
+ bbox=dict(boxstyle='round,pad=0.5', facecolor='lightgreen', alpha=0.9),
+ arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.2',
+ color='green', lw=2))
+
+ # 标注最大曲率点(如果不同)
+ if abs(methods_info['max_curvature']['years'] - rec['years']) > 5:
+ mc = methods_info['max_curvature']
+ ax.annotate(f"● Max Curvature\n{mc['years']:.0f}y | {mc['energy_PJ']:.0f}PJ",
+ xy=(mc['years'], mc['energy_PJ']),
+ xytext=(mc['years'] - 20, mc['energy_PJ'] - 2000),
+ fontsize=10,
+ bbox=dict(boxstyle='round,pad=0.3', facecolor='lightyellow', alpha=0.8),
+ arrowprops=dict(arrowstyle='->', color='red', lw=1.5))
+
+ # 标记端点
+ ax.scatter(T[0], E[0], c='red', s=150, marker='D', edgecolors='black', zorder=5)
+ ax.annotate(f'Fastest\n{T[0]:.0f}y, {E[0]:.0f}PJ',
+ xy=(T[0], E[0]), xytext=(T[0]+5, E[0]-1500),
+ fontsize=10, ha='left')
+
+ ax.scatter(T[-1], E[-1], c='green', s=150, marker='D', edgecolors='black', zorder=5)
+ ax.annotate(f'Lowest Energy\n{T[-1]:.0f}y, {E[-1]:.0f}PJ',
+ xy=(T[-1], E[-1]), xytext=(T[-1]-25, E[-1]+1500),
+ fontsize=10, ha='right')
+
+ ax.set_xlabel('Completion Time (years)', fontsize=14)
+ ax.set_ylabel('Total Energy Consumption (PJ)', fontsize=14)
+ ax.set_title(f'Optimal Decision Point for Combined Scenario\n'
+ f'(Elevator + Rockets, {year_min:.0f}-{year_max:.0f} years)', fontsize=16)
+ ax.grid(True, alpha=0.3)
+
+ # 图例
+ from matplotlib.lines import Line2D
+ legend_elements = [
+ Line2D([0], [0], marker='*', color='w', markerfacecolor='green', markersize=20,
+ markeredgecolor='black', label='Max Distance (Recommended)'),
+ Line2D([0], [0], marker='o', color='w', markerfacecolor='red', markersize=12,
+ markeredgecolor='black', label='Max Curvature'),
+ Line2D([0], [0], marker='^', color='w', markerfacecolor='orange', markersize=12,
+ markeredgecolor='black', label='Elbow Method'),
+ ]
+ ax.legend(handles=legend_elements, loc='upper right', fontsize=11)
+
+ # 右侧信息框
+ scenario = calculate_scenario(rec['years'])
+ textstr = (f"Knee Point Analysis\n"
+ f"─────────────────\n"
+ f"Optimal Time: {rec['years']:.0f} years\n"
+ f"Total Energy: {rec['energy_PJ']:.0f} PJ\n"
+ f"─────────────────\n"
+ f"Elevator: {scenario['elevator_payload']/1e6:.1f}M tons\n"
+ f"Rockets: {scenario['rocket_payload']/1e6:.1f}M tons\n"
+ f"Launches: {scenario['rocket_launches']:,}\n"
+ f"Sites Used: {scenario['sites_used']}")
+
+ props = dict(boxstyle='round', facecolor='white', alpha=0.9, edgecolor='gray')
+ ax.text(0.02, 0.35, textstr, transform=ax.transAxes, fontsize=11,
+ verticalalignment='top', horizontalalignment='left', bbox=props,
+ family='monospace')
+
+ plt.tight_layout()
+ plt.savefig(save_path, dpi=150, bbox_inches='tight')
+ print(f"组合方案决策图已保存至: {save_path}")
+
+ return fig
+
+
+def print_combined_range_report(df: pd.DataFrame, knee_analysis: Dict, year_range: Tuple[float, float]):
+ """
+ 打印组合方案范围内的分析报告
+ """
+ year_min, year_max = year_range
+
+ print("\n" + "=" * 80)
+ print("COMBINED SCENARIO PARETO ANALYSIS (Elevator + Rockets)")
+ print(f"Analysis Range: {year_min:.0f} - {year_max:.0f} years")
+ print("=" * 80)
+
+ print(f"\n1. RANGE DEFINITION")
+ print(f" - Minimum time (all resources): {year_min:.0f} years")
+ print(f" - Elevator-only time: {year_max:.0f} years")
+ print(f" - This range requires BOTH elevator and rockets")
+
+ print(f"\n2. PARETO FRONT STATISTICS")
+ print(f" - Energy at fastest ({year_min:.0f}y): {df['energy_PJ'].max():.0f} PJ")
+ print(f" - Energy at slowest ({year_max:.0f}y): {df['energy_PJ'].min():.0f} PJ")
+ print(f" - Energy reduction: {df['energy_PJ'].max() - df['energy_PJ'].min():.0f} PJ "
+ f"({(1 - df['energy_PJ'].min()/df['energy_PJ'].max())*100:.1f}%)")
+
+ print(f"\n3. KNEE POINT DETECTION RESULTS")
+ print("-" * 70)
+ print(f" {'Method':<20} {'Years':>8} {'Energy(PJ)':>12} {'Elev%':>8} {'Rocket%':>8}")
+ print("-" * 70)
+
+ for method, data in knee_analysis['methods'].items():
+ rocket_pct = (1 - data['elevator_fraction']) * 100
+ print(f" {method:<20} {data['years']:>8.0f} {data['energy_PJ']:>12.0f} "
+ f"{data['elevator_fraction']*100:>7.1f}% {rocket_pct:>7.1f}%")
+
+ print("-" * 70)
+
+ rec = knee_analysis['recommended']
+ scenario = calculate_scenario(rec['years'])
+
+ print(f"\n4. RECOMMENDED OPTIMAL POINT")
+ print(f" ╔═══════════════════════════════════════════════════════╗")
+ print(f" ║ Method: Max Distance (most robust) ║")
+ print(f" ║ Completion Time: {rec['years']:>6.0f} years ║")
+ print(f" ║ Total Energy: {rec['energy_PJ']:>6.0f} PJ ║")
+ print(f" ║ Elevator Payload: {scenario['elevator_payload']/1e6:>6.1f} M tons ({rec['elevator_fraction']*100:.0f}%) ║")
+ print(f" ║ Rocket Payload: {scenario['rocket_payload']/1e6:>6.1f} M tons ({(1-rec['elevator_fraction'])*100:.0f}%) ║")
+ print(f" ║ Total Launches: {scenario['rocket_launches']:>6,} ║")
+ print(f" ║ Sites Required: {scenario['sites_used']:>6} ║")
+ print(f" ╚═══════════════════════════════════════════════════════╝")
+
+ print(f"\n5. TRADE-OFF ANALYSIS")
+
+ # 与最快方案对比
+ fast_scenario = calculate_scenario(year_min + 0.5)
+ if fast_scenario:
+ time_gain = rec['years'] - year_min
+ energy_save = fast_scenario['total_energy_PJ'] - rec['energy_PJ']
+ print(f"\n vs. Fastest ({year_min:.0f}y):")
+ print(f" - Extra time: +{time_gain:.0f} years")
+ print(f" - Energy saved: {energy_save:.0f} PJ ({energy_save/fast_scenario['total_energy_PJ']*100:.1f}%)")
+ print(f" - Efficiency: {energy_save/time_gain:.1f} PJ saved per extra year")
+
+ # 与纯电梯对比
+ elev_scenario = calculate_scenario(year_max - 0.5)
+ if elev_scenario:
+ time_save = year_max - rec['years']
+ energy_cost = rec['energy_PJ'] - elev_scenario['total_energy_PJ']
+ print(f"\n vs. Elevator-only ({year_max:.0f}y):")
+ print(f" - Time saved: {time_save:.0f} years")
+ print(f" - Extra energy: {energy_cost:.0f} PJ ({energy_cost/elev_scenario['total_energy_PJ']*100:.1f}%)")
+ print(f" - Cost: {energy_cost/time_save:.1f} PJ per year saved")
+
+ print("\n" + "=" * 80)
+ print("INTERPRETATION:")
+ print(f"The knee point at {rec['years']:.0f} years represents the optimal trade-off")
+ print("where further extending the timeline yields diminishing returns,")
+ print("while shortening it incurs disproportionately higher energy costs.")
+ print("=" * 80)
+
+
+# ============== 主程序 ==============
+
+if __name__ == "__main__":
+ print("=" * 80)
+ print("PARETO FRONT & KNEE POINT OPTIMIZATION")
+ print("Space Elevator + Rocket Combination for Moon Colony")
+ print("=" * 80)
+
+ # ===== 分析1: 组合方案范围 (101-186年) =====
+ print("\n" + "=" * 80)
+ print("ANALYSIS 1: Combined Scenario Range (Elevator + Rockets Required)")
+ print("=" * 80)
+
+ df_combined, year_min, year_max = analyze_combined_range()
+ knee_combined = analyze_knee_points(df_combined)
+
+ print_combined_range_report(df_combined, knee_combined, (year_min, year_max))
+
+ plot_combined_range_analysis(df_combined, knee_combined, (year_min, year_max))
+ plot_combined_decision(df_combined, knee_combined, (year_min, year_max))
+
+ # ===== 分析2: 完整范围 (100-300年) =====
+ print("\n" + "=" * 80)
+ print("ANALYSIS 2: Full Range (100-300 years)")
+ print("=" * 80)
+
+ df_full = generate_pareto_front(year_min=100, year_max=300, num_points=500)
+ knee_full = analyze_knee_points(df_full)
+
+ print_analysis_report(df_full, knee_full)
+ plot_pareto_analysis(df_full, knee_full)
+ plot_decision_recommendation(df_full, knee_full)
+
+ # 保存数据
+ df_combined.to_csv('/Volumes/Files/code/mm/20260130_b/pareto_combined_range.csv', index=False)
+ df_full.to_csv('/Volumes/Files/code/mm/20260130_b/pareto_front_data.csv', index=False)
+ print("\n数据已保存至 CSV 文件")
+
+ print("\n" + "=" * 80)
+ print("分析完成!")
+ print("=" * 80)