← 文章 / 开源项目
Hacker News 7小时前 · 2026-09-16 20:04:47 · 4 阅读

利用开源 Scikit-decide 框架优化飞行航路以节省航空燃油

一架波音 787-9 Dreamliner 从纽瓦克自由国际机场(EWR)直飞列奥纳多·达·芬奇-菲乌米奇诺机场(FCO)的 8.5 小时航程中,可能消耗价值 6.8 万美元的航空燃油。若根据风况调整航路,可降低燃油消耗,或许还能省下数千美元。

Jeppesen 等公司在此领域有相关方案,但结合 Delft University of Technology 某教授构建的窄体及宽体机燃油消耗模型,以及来自 NOAA 的风数据,Scikit-decide 提供了一套开源解决方案。

Scikit-decide 已经研发六年,是一个强化学习、自动化规划与调度的框架。该项目可优化飞行航路、重组航空员工排班,以及计算无人机群飞行路径。

OpenAP 是由 Junzi Sun 博士开发的飞机性能模型和工具包。Sun 博士拥有空中交通管理博士学位,在荷兰 TU Delft 担任终身副教授,并开设相关课程。

Scikit-decide 的航路优化求解器支持配置不同的燃油消耗模型。本文对比了使用 Airbus A320 结合 OpenAP 燃油消耗模型飞行的两条航路。

我的工作站

我使用 5.7 GHz 的 AMD Ryzen 9 9950X CPU,配备 16 核心 32 线程,拥有 1.2 MB L1、16 MB L2 和 64 MB L3 缓存。系统连接了液冷散热器,安装在宽敞的全尺寸 Cooler Master HAF 700 机箱中。

系统配备 96 GB DDR5 内存,运行频率 4,800 MT/s,以及一块 5 代 Crucial T700 4 TB NVMe M.2 SSD,读取速度最高可达 12,400 MB/s。SSD 上加装了散热器以控制温度,这块 SSD 是系统的 C 盘。

系统由 1,200 瓦全模组 Corsair 电源供电,安装在 ASRock X870E Nova 90 主板上。

我在 Windows 11 Pro 上通过 Microsoft 的 Ubuntu for Windows 运行 Ubuntu 24 LTS。至于为何不将基于 Linux 的桌面作为主工作环境,一方面是我仍在使用 Nvidia GTX 1080 GPU,其在 Windows 下的驱动支持更好,另一方面 ArcGIS Pro 仅在 Windows 上提供原生支持。

安装依赖项

本文将使用 Python 3.12 和 jq。

$ sudo add-apt-repository ppa:deadsnakes/ppa
$ sudo apt update
$ sudo apt install \
    jq \
    python3-pip \
    python3.12-venv

我会创建一个 Python 虚拟环境,并安装 scikit-decide、OpenAP 开源飞机性能模型以及 OpenTop。OpenTop 是一套飞行轨迹工具包,同样由 Sun 博士开发。

$ python3 -m venv ~/.flight_planning
$ source ~/.flight_planning/bin/activate
$ pip install \
    'scikit-decide[all]' \
    'openap[all]' \
    opentop

上述操作至少需要 8 GB 的存储空间。以下是已安装的软件包列表。

$ pip install pipdeptree
$ pipdeptree -d0
lz4==4.4.5
openevolve==0.3.2
opentop==2.6.0
pip==24.0
pipdeptree==4.2.5
plado==0.1.6
pygeodesy==26.9.9
pygrib==2.1.8
pyRDDLGym-gurobi==0.2
pyRDDLGym-jax==3.1
pyRDDLGym-rl==0.2
pytz==2026.3.post1
ray==2.37.0
rddlrepository==2.2
sb3_contrib==2.3.0
scikit-decide==1.1.1
scikit-image==0.26.0
tensorboardX==2.6.5
torch-geometric==2.8.0.post1
typer==0.27.2
unified-planning==1.2.0
up-enhsp==0.0.27
up_fast_downward==0.5.2
up-pyperplan==1.1.0
z3-solver==5.1.0.0

本文将使用 DuckDB 及其 H3JSONLindelParquetSpatial 扩展。

$ cd ~
$ wget -c https://github.com/duckdb/duckdb/releases/download/v1.5.4/duckdb_cli-linux-amd64.zip
$ unzip -j duckdb_cli-linux-amd64.zip
$ chmod +x duckdb
$ ~/duckdb
INSTALL h3 FROM community;
INSTALL lindel FROM community;
INSTALL json;
INSTALL parquet;
INSTALL spatial;

我会配置 DuckDB,使其在每次启动时自动加载所有已安装的扩展。

$ vi ~/.duckdbrc
.timer on
.width 180
LOAD h3;
LOAD lindel;
LOAD json;
LOAD parquet;
LOAD spatial;

本文中的地图使用 QGIS 4.2.1 版本渲染。QGIS 是一款可在 Windows、macOS 和 Linux 上运行的桌面应用。近年来,QGIS 的人气飙升,每月在全球范围内约有 2200 万次启动。

边界与地名数据来自 Natural Earth,海洋边界数据来自 Marine Regions

OpenAP 的机型数据

首先把 OpenAP 的代码仓库克隆下来。

$ git clone https://github.com/junzis/openap

除去单元测试和工具脚本,这个包共有 3,369 行 Python 代码。

OpenAP 的模型依赖大量数据集,都打包在代码库里,涵盖各种机型。下面是各飞机制造商的数量统计。

$ grep -ho 'aircraft: .*[a-z] ' \
    openap/data/aircraft/*.yml \
    | cut -d' ' -f2 \
    | sort \
    | uniq -c \
    | sort -rn
17 Boeing
13 Airbus
 5 Embraer
 1 Gulfstream
 1 Cessna

下面是 Airbus A380-800 的各项参数。

$ cat openap/data/aircraft/a388.yml
aircraft: Airbus A380-800

mtow: 560000
mlw: 386000
oew: 277000
mfc: 320000
vmo: 340
mmo: 0.89
ceiling: 13100

pax:
  max: 853
  low: 410
  high: 620

fuselage:
  length: 72.72
  height: 8.41
  width: 7.14

wing:
  area: 845
  span: 79.75
  mac: null
  sweep: 33.5
  t/c: 0.08

flaps:
  type: single-slotted
  area: null
  bf/b: null
  lambda_f: 0.900
  cf/c: 0.150
  Sf/S: 0.150

cruise:
  height: 12800
  mach: 0.85
  range: 14800

engine:
  type: turbofan
  mount: wing
  number: 4
  default: GP7270
  options:
    A380-841: Trent 970-84
    A380-842: Trent 972-84
    A380-861: GP7270

drag:
  cd0: 0.016
  k: 0.050
  e: 0.855
  gears: 0.012

这是它的阻力系数。

$ cat openap/data/dragpolar/a388.yml
aircraft: Airbus A380-800

clean:
  cd0:         0.016
  k:           0.050
  e:           0.855

gears:         0.012

flaps:
  lambda_f:    0.900
  cf/c:        0.150
  Sf/S:        0.150

还有一些其他参数。

$ echo "import pandas as pd; print(
            pd.read_fwf('openap/data/wrap/a388.txt')
              .to_csv(index=False))" \
    | python3 \
    | ~/duckdb \
        -c '.maxwidth 150' \
        -c "SELECT * EXCLUDE(parameters),
                   parameters: SPLIT(parameters, '|')
            FROM   READ_CSV('/dev/stdin')"
┌──────────────────────┬────────────────┬───────────────────────────────────────┬────────┬────────┬─────────┬─────────┬──────────────────────────────┐
│       变量名          │   飞行阶段     │                 名称                  │  最优值 │  最小值  │  最大值   │ 分布   │          参数            │
│       字符串         │    字符串      │                字符串                │  双精度 │  双精度  │  双精度  │ 字符串  │          数组            │
├──────────────────────┼────────────────┼───────────────────────────────────────┼────────┼────────┼─────────┼─────────┼──────────────────────────────┤
│ to_v_lof             │ 起飞           │ 离地速度                              │   89.9 │   75.4 │   104.4 │ 正态   │ [89.93, 10.07]               │
│ to_d_tof             │ 起飞           │ 起飞距离                              │   2.56 │   1.35 │    3.78 │ 正态   │ [2.56, 0.74]                 │
│ to_acc_tof           │ 起飞           │ 平均起飞加速度                        │   1.35 │   1.04 │    1.66 │ 正态   │ [1.35, 0.19]                 │
│ ic_va_avg            │ 初始爬升       │ 平均空速                              │   88.0 │   80.0 │    96.0 │ 正态   │ [88.15, 5.64]                │
│ ic_vs_avg            │ 初始爬升       │ 平均垂直上升率                        │   5.65 │    4.4 │    8.94 │ 伽马   │ [4.76, 3.22, 0.65]           │
│ cl_d_range           │ 爬升           │ 爬升航段距离                          │  296.0 │  200.0 │   446.0 │ 贝塔   │ [3.23, 5.18, 179.46, 335.24] │
│ cl_v_cas_const       │ 爬升           │ 定表速(CAS)                        │  163.0 │  155.0 │   170.0 │ 正态   │ [163.39, 4.51]               │
│ cl_v_mach_const      │ 爬升           │ 定马赫数                             │   0.84 │    0.8 │    0.86 │ 贝塔   │ [12.23, 5.32, 0.72, 0.17]    │
│ cl_h_cas_const       │ 爬升           │ 定表速过渡高度                        │    3.3 │    1.3 │     5.3 │ 正态   │ [3.29, 1.24]                 │
│ cl_h_mach_const      │ 爬升           │ 定马赫数过渡高度                      │    8.9 │    8.2 │     9.7 │ 正态   │ [8.94, 0.47]                 │
│ cl_vs_avg_pre_cas    │ 爬升           │ 定表速前平均爬升率                    │   7.85 │   5.95 │    9.75 │ 正态   │ [7.85, 1.16]                 │
│ cl_vs_avg_cas_const  │ 爬升           │ 定表速段平均爬升率                    │   7.51 │    5.2 │    9.82 │ 正态   │ [7.51, 1.40]                 │
│ cl_vs_avg_mach_const │ 爬升           │ 定马赫数段平均爬升率                  │   5.56 │   3.23 │    7.91 │ 正态   │ [5.57, 1.42]                 │
│ cr_d_range           │ 巡航           │ 巡航航段距离                          │ 4348.0 │  892.0 │ 20565.0 │ 伽马   │ [2.81, 246.73, 2274.81]      │
│ cr_v_cas_mean        │ 巡航           │ 平均巡航表速                          │  136.0 │  130.0 │   145.0 │ 贝塔   │ [3.32, 5.27, 126.00, 29.75]  │
│ cr_v_cas_max         │ 巡航           │ 最大巡航表速                          │  145.0 │  134.0 │   164.0 │ 贝塔   │ [2.02, 3.21, 130.38, 46.65]  │
│ cr_v_mach_mean       │ 巡航           │ 平均巡航马赫数                        │   0.84 │   0.82 │    0.86 │ 正态   │ [0.84, 0.01]                 │
│ cr_v_mach_max        │ 巡航           │ 最大巡航马赫数                        │   0.87 │   0.85 │     0.9 │ 伽马   │ [16.14, 0.80, 0.00]          │
│ cr_h_init            │ 巡航           │ 初始巡航高度                          │  11.55 │    9.3 │   12.23 │ 贝塔   │ [3.82, 1.66, 7.49, 5.01]     │
│ cr_h_mean            │ 巡航           │ 平均巡航高度                          │  11.73 │  10.87 │   12.28 │ 贝塔   │ [7.22, 3.92, 9.59, 3.14]     │
│ cr_h_max             │ 巡航           │ 最大巡航高度                          │  12.06 │  11.52 │    12.6 │ 正态   │ [12.06, 0.33]                │
│ de_d_range           │ 下降           │ 下降航段距离                          │  310.0 │  238.0 │   528.0 │ 伽马   │ [4.73, 213.47, 25.87]        │
│ de_v_mach_const      │ 下降           │ 定马赫数                             │   0.83 │    0.8 │    0.87 │ 正态   │ [0.83, 0.02]                 │
│ de_v_cas_const       │ 下降           │ 定表速(CAS)                        │  154.0 │  142.0 │   167.0 │ 正态   │ [154.84, 7.74]               │
│ de_h_mach_const      │ 下降           │ 定马赫数过渡高度                      │   10.1 │    8.6 │    11.5 │ 正态   │ [10.06, 0.88]                │
│ de_h_cas_const       │ 下降           │ 定表速过渡高度                        │    6.6 │    3.9 │     9.4 │ 正态   │ [6.64, 1.69]                 │
│ de_vs_avg_mach_const │ 下降           │ 定马赫数段平均下降率                  │  -6.06 │  -11.9 │   -2.97 │ 贝塔   │ [3.43, 2.08, -15.98, 14.36]  │
│ de_vs_avg_cas_const  │ 下降           │ 定表速段平均下降率                    │  -8.36 │ -11.74 │   -4.97 │ 正态   │ [-8.36, 2.06]                │
│ de_vs_avg_after_cas  │ 下降           │ 定表速后平均下降率                    │  -5.48 │  -6.93 │   -4.02 │ 正态   │ [-5.48, 0.88]                │
│ fa_va_avg            │ 最后进近       │ 平均空速                              │   73.0 │   68.0 │    77.0 │ 正态   │ [73.28, 3.02]                │
│ fa_vs_avg            │ 最后进近       │ 平均垂直上升率                        │  -3.71 │  -4.13 │   -2.92 │ 伽马   │ [9.49, -4.74, 0.12]          │
│ fa_agl               │ 最后进近       │ 进近角                                │    2.9 │   2.42 │    3.38 │ 正态   │ [2.90, 0.29]                 │
│ ld_v_app             │ 着陆           │ 触地速度                              │   70.0 │   62.1 │    78.0 │ 正态   │ [70.00, 5.52]                │
│ ld_d_brk             │ 着陆           │ 刹车距离                              │   2.26 │   0.73 │     3.8 │ 正态   │ [2.26, 0.93]                 │
│ ld_acc_brk           │ 着陆           │ 平均刹车加速度                        │  -1.01 │  -1.51 │   -0.52 │ 正态   │ [-1.01, 0.30]                │
└──────────────────────┴────────────────┴───────────────────────────────────────┴────────┴────────┴─────────┴─────────┴──────────────────────────────┘

这是飞机型号同义词列表。

$ ~/duckdb -c "FROM READ_CSV('/dev/stdin')" \
    < openap/data/aircraft/_synonym.csv
┌─────────┬─────────┐
│  orig   │   new   │
│ varchar │ varchar │
├─────────┼─────────┤
│ a124    │ b744    │
│ a306    │ a332    │
│ a310    │ a318    │
│ at72    │ e145    │
│ at75    │ e145    │
│ at76    │ e145    │
│ b733    │ b734    │
│ b735    │ b734    │
│ b762    │ b763    │
│ b77l    │ b77w    │
│ c25a    │ c550    │
│ c525    │ c550    │
│ c56x    │ c550    │
│ crj2    │ e145    │
│ crj9    │ e75l    │
│ e290    │ e190    │
│ glf5    │ glf6    │
│ gl5t    │ glf6    │
│ lj45    │ glf6    │
│ md11    │ b773    │
│ pc24    │ c550    │
│ su95    │ e170    │
└─────────┴─────────┘

航空发动机

飞机通常至少提供两种不同的发动机供选择。该数据包的数据集中共列出了 427 台发动机。

$ wc -l openap/data/engine/engines.csv # 427

以下是 Trent 970-84 发动机的详细信息。

$ echo "FROM  'openap/data/engine/engines.csv'
        WHERE name = 'Trent 970-84'
        LIMIT 1" \
    | ~/duckdb -json \
    | jq -S .
[
  {
    "bpr": 8.45,
    "cruise_alt": null,
    "cruise_mach": null,
    "cruise_sfc": null,
    "cruise_thrust": null,
    "ei_co_app": 1.16,
    "ei_co_co": 0.31,
    "ei_co_idl": 13.38,
    "ei_co_to": 0.32,
    "ei_hc_app": 0.08,
    "ei_hc_co": 0.12,
    "ei_hc_idl": 0.04,
    "ei_hc_to": 0.02,
    "ei_nox_app": 12.09,
    "ei_nox_co": 29.42,
    "ei_nox_idl": 5.44,
    "ei_nox_to": 38.29,
    "ff_app": 0.72,
    "ff_co": 2.157,
    "ff_idl": 0.255,
    "ff_to": 2.605,
    "fuel_lto": 965.0,
    "manufacturer": "Rolls-Royce plc",
    "max_thrust": 338700.0,
    "name": "Trent 970-84",
    "pr": 38.0,
    "type": "TF",
    "uid": "18RR081"
  }
]

以下是各发动机制造商的数量统计。

$ ~/duckdb
CREATE OR REPLACE TABLE a AS
    FROM 'openap/data/engine/engines.csv';

SELECT   COUNT(*),
         manufacturer
FROM     a
GROUP BY 2
ORDER BY 1 DESC;
┌──────────────┬────────────────────────────┐
│ count_star() │        manufacturer        │
│    int64     │          varchar           │
├──────────────┼────────────────────────────┤
│          108 │ GE Aircraft Engines        │
│           94 │ CFM International          │
│           85 │ Pratt & Whitney            │
│           62 │ Rolls-Royce plc            │
│           13 │ International Aero Engines │
│           12 │ Pratt & Whitney Canada     │
│           11 │ Rolls-Royce Corporation    │
│            8 │ Rolls-Royce Deutschland    │
│            8 │ Honeywell                  │
│            7 │ Aviadvigatel               │
│            5 │ Textron Lycoming           │
│            4 │ KKBM                       │
│            3 │ IVCHENKO PROGRESS ZMBK     │
│            2 │ PowerJet S.A.              │
│            2 │ Allied Signal              │
│            1 │ Engine Alliance            │
│            1 │ Garret AiResearch          │
└──────────────┴────────────────────────────┘

这是数据集中涡扇(TF)、混流涡扇(MTF)、涡桨(TP)和活塞(PS)发动机的类型统计。

SELECT   COUNT(*),
         type
FROM     a
GROUP BY 2
ORDER BY 1 DESC;
┌──────────────┬─────────┐
│ count_star() │  type   │
│    int64     │ varchar │
├──────────────┼─────────┤
│          322 │ TF      │
│           98 │ MTF     │
│            5 │ TP      │
│            1 │ PS      │
└──────────────┴─────────┘

这是按最大推力排序的发动机列表。

SELECT   manufacturer,
         name,
         type,
         max_thrust
FROM     a
ORDER BY 4 DESC
LIMIT    25;
┌─────────────────────┬───────────────┬─────────┬────────────┐
│    manufacturer     │     name      │  type   │ max_thrust │
│       varchar       │    varchar    │ varchar │   double   │
├─────────────────────┼───────────────┼─────────┼────────────┤
│ GE Aircraft Engines │ GE90-115B     │ TF      │   513900.0 │
│ GE Aircraft Engines │ GE90-113B     │ TF      │   504900.0 │
│ GE Aircraft Engines │ GE90-110B1    │ TF      │   492600.0 │
│ Rolls-Royce plc     │ Trent XWB-97  │ TF      │   436748.0 │
│ GE Aircraft Engines │ GE90-94B      │ TF      │   430920.0 │
│ GE Aircraft Engines │ GE90-92B      │ TF      │   426720.0 │
│ GE Aircraft Engines │ GE90-90B      │ TF      │   419250.0 │
│ Rolls-Royce plc     │ Trent 895     │ TF      │   413050.0 │
│ Rolls-Royce plc     │ Trent 892     │ TF      │   411480.0 │
│ Pratt & Whitney     │ PW4090        │ TF      │   408300.0 │
│ GE Aircraft Engines │ GE90-85B      │ TF      │   397210.0 │
│ Rolls-Royce plc     │ Trent 884     │ TF      │   390100.0 │
│ Pratt & Whitney     │ PW4084D       │ TF      │   385900.0 │
│ Rolls-Royce plc     │ Trent XWB-84  │ TF      │   379000.0 │
│ Pratt & Whitney     │ PW4084        │ TF      │   369600.0 │
│ GE Aircraft Engines │ GE90-77B      │ TF      │   366750.0 │
│ Rolls-Royce plc     │ Trent 1000-R3 │ TF      │   363900.0 │
│ GE Aircraft Engines │ GE90-76B      │ TF      │   363220.0 │
│ Rolls-Royce plc     │ Trent 877     │ TF      │   361640.0 │
│ Rolls-Royce plc     │ Trent 1000-M3 │ TF      │   358100.0 │
│ Rolls-Royce plc     │ Trent 1000-N3 │ TF      │   358100.0 │
│ Pratt & Whitney     │ PW4077D       │ TF      │   355700.0 │
│ Rolls-Royce plc     │ Trent XWB-79B │ TF      │   355200.0 │
│ Rolls-Royce plc     │ Trent XWB-79  │ TF      │   355200.0 │
│ Rolls-Royce plc     │ Trent 970B-84 │ TF      │   352900.0 │
└─────────────────────┴───────────────┴─────────┴────────────┘

这是燃料模型的默认值和覆盖设置。

$ ~/duckdb -c "FROM READ_CSV('/dev/stdin')" \
    < openap/data/fuel/fuel_models.csv
┌──────────┬─────────────┬────────────────────┬────────────────────┬────────────────────┐
│ typecode │ engine_type │         c1         │         c2         │         c3         │
│ varchar  │   varchar   │       double       │       double       │       double       │
├──────────┼─────────────┼────────────────────┼────────────────────┼────────────────────┤
│ A318     │ CFM56-5B9/3 │ 0.7769784596099123 │  1.765377288174942 │ 2.5349134936316693 │
│ A319     │ V2524-A5    │ 0.8694169413032631 │ 1.9542690629047836 │ 2.5028187026860103 │
│ A320     │ CFM56-5B4/P │ 1.0453208160586924 │ 2.3633720747416573 │ 1.2378127479131922 │
│ A321     │ V2533-A5    │ 1.3979999999999444 │  2.054028451829268 │ 1.0008941993511127 │
│ A332     │ Trent 772   │  2.886430057340283 │ 1.0960397632560752 │ 2.3772585567580293 │
│ A333     │ Trent 772   │ 3.1199999999999997 │ 1.0365152289922772 │  1.950599421257047 │
│ B737     │ CFM56-7B26  │ 1.0237419750954273 │ 1.4670109921175798 │ 3.2566140275646456 │
│ B738     │ CFM56-7B26E │  1.075484518912494 │ 1.8777303165419037 │ 1.8895522140156369 │
│ B739     │ CFM56-7B27E │ 1.3079999999999998 │ 1.5986016771932572 │ 1.2789091908108752 │
│ CRJ9     │ CF34-8C5    │ 0.6437136288905128 │ 1.9690234662778772 │ 1.4375859706162741 │
│ E170     │ CF34-8E5    │ 0.6341784688704629 │  2.778729428440142 │ 1.0149695061665696 │
│ E190     │ CF34-10E5   │ 0.8339999999998783 │ 2.3343013671118475 │ 0.4847704716061958 │
│ E195     │ CF34-10E5A1 │  0.911999999999993 │  1.929664699695295 │ 0.8452746256489131 │
│ E75L     │ CF34-8E5    │ 0.6340709359225759 │  2.614653287356019 │ 0.8714282723568036 │
│ default  │ default     │  0.937564901246902 │ 1.9767611682280135 │ 1.3954794843472482 │
└──────────┴─────────────┴────────────────────┴────────────────────┴────────────────────┘

机场与导航

该包附带了约 1.4 万个机场的位置和代码数据。

$ wc -l openap/data/nav/airports.csv # 13796

$ ~/duckdb -c "FROM READ_CSV('/dev/stdin')
               WHERE country = 'CA'
               ORDER BY lat
               LIMIT 20" \
    < openap/data/nav/airports.csv
┌─────────┬──────────┬───────────┬───────┬─────────┬───────────────────────────────┬────────────────┐
│  icao   │   lat    │    lon    │  alt  │ country │             name              │    location    │
│ varchar │  double  │  double   │ int64 │ varchar │            varchar            │    varchar     │
├─────────┼──────────┼───────────┼───────┼─────────┼───────────────────────────────┼────────────────┤
│ CYQG    │ 42.27334 │ -82.97056 │   622 │ CA      │ Windsor                       │ Windsor        │
│ CYQS    │ 42.77202 │ -81.11923 │   778 │ CA      │ St Thomas Muni                │ St. Thomas     │
│ CYZR    │ 43.00444 │ -82.31528 │   594 │ CA      │ Sarnia - Chris Hadfield       │ Sarnia         │
│ CYXU    │ 43.04211 │  -81.1598 │   912 │ CA      │ London                        │ London         │
│ CYFD    │ 43.12389 │ -80.34667 │   815 │ CA      │ Brantford                     │ Brant          │
│ CYHM    │ 43.18056 │ -79.95306 │   780 │ CA      │ John C Munro Hamilton Intl    │ Ancaster       │
│ CYSN    │ 43.18792 │  -79.1786 │   321 │ CA      │ Niagara District              │ St. Catharines │
│ CYCE    │ 43.28306 │ -81.51806 │   824 │ CA      │ Huron Airpark                 │ South Huron    │
│ CYSA    │ 43.41087 │ -80.93994 │  1215 │ CA      │ Stratford Municipal           │ Stratford      │
│ CZBA    │   43.445 │ -79.85472 │   602 │ CA      │ Burlington Airpark            │ Burlington     │
│ CYKF    │ 43.45694 │ -80.39056 │  1054 │ CA      │ Waterloo                      │ Cambridge      │
│ CYTZ    │ 43.62747 │ -79.40336 │   251 │ CA      │ Toronto City Centre           │ Toronto        │
│ CYYZ    │ 43.66073 │ -79.62394 │   568 │ CA      │ Toronto Lester B Pearson Intl │ Etobicoke      │
│ CYZD    │ 43.74972 │ -79.47417 │   652 │ CA      │ Downsview                     │ Concord        │
│ CYGD    │ 43.77111 │ -81.71639 │   712 │ CA      │ Goderich                      │ Goderich       │
│ CYQI    │  43.8175 │  -66.0975 │   141 │ CA      │ Yarmouth                      │ Yarmouth       │
│ CYKZ    │ 43.86444 │ -79.37334 │   650 │ CA      │ Buttonville Muni              │ Richmond Hill  │
│ CYOO    │ 43.92444 │ -78.90389 │   459 │ CA      │ Oshawa                        │ Oshawa         │
│ CYTR    │ 44.10889 │ -77.54222 │   283 │ CA      │ Trenton                       │ Quinte West    │
│ CYGK    │ 44.21833 │ -76.60083 │   305 │ CA      │ Kingston                      │ Kingston       │
└─────────┴──────────┴───────────┴───────┴─────────┴───────────────────────────────┴────────────────┘

这些机场分布在 236 个国家。

$ ~/duckdb
SELECT COUNT(DISTINCT country)
FROM   READ_CSV('openap/data/nav/airports.csv');
236

以下是机场数据集中机场数量最多的国家。

SELECT   COUNT(*),
         country
FROM     READ_CSV('openap/data/nav/airports.csv')
GROUP BY 2
ORDER BY 1 DESC
LIMIT    20;
┌──────────────┬─────────┐
│ count_star() │ country │
│    int64     │ varchar │
├──────────────┼─────────┤
│         2849 │ BR      │
│         2459 │ US      │
│         2062 │ AU      │
│          441 │ FR      │
│          345 │ CA      │
│          325 │ DE      │
│          258 │ GB      │
│          234 │ ID      │
│          176 │ NA      │
│          168 │ VE      │
│          156 │ RU      │
│          148 │ IN      │
│          143 │ AR      │
│          139 │ SE      │
│          126 │ JP      │
│          118 │ IT      │
│          106 │ NZ      │
│           99 │ CZ      │
│           98 │ BO      │
│           97 │ ZA      │
└──────────────┴─────────┘

这是部分导航航路点。

$ echo "import pandas as pd; print(
            pd.read_fwf('openap/data/nav/fix.dat',
                        skiprows=3,
                        header=None,
                        encoding='unicode_escape')
              .to_csv(index=False))" \
    | python3 \
    | ~/duckdb \
        -c '.maxwidth 150' \
        -c "FROM   READ_CSV('/dev/stdin')
            WHERE  column0 BETWEEN 57 AND 59
            AND    column1 BETWEEN 21 AND 27
            LIMIT 20"
┌───────────┬───────────┬─────────┐
│  column0  │  column1  │ column2 │
│  double   │  double   │ varchar │
├───────────┼───────────┼─────────┤
│ 57.133196 │ 23.888414 │ ALISA   │
│ 57.105833 │ 25.254167 │ AMOLI   │
│ 58.416389 │ 24.478333 │ ANAMA   │
│ 58.412778 │ 22.521667 │ EIKLA   │
│ 58.506944 │ 25.715278 │ EKLON   │
│ 58.626667 │ 21.766111 │ EVERI   │
│ 57.278611 │ 25.050556 │ GEKLI   │
│   58.9425 │ 25.576944 │ GONOS   │
│ 58.053333 │ 26.762778 │ KANEP   │
│ 58.331944 │ 22.221111 │ KARLA   │
│   58.7225 │ 24.586944 │ KEMET   │
│ 58.725753 │ 26.736943 │ KOLEV   │
│ 58.708056 │ 22.845833 │ KUKET   │
│ 58.931111 │ 24.661944 │ KUNUX   │
│ 58.176667 │     26.93 │ KUUST   │
│ 58.441667 │ 26.451667 │ LAEVA   │
│ 58.553333 │ 25.934444 │ LALSI   │
│ 57.336944 │ 22.636944 │ LAPSA   │
│ 57.774167 │ 22.104444 │ LATEG   │
│ 58.180278 │ 25.779167 │ LATKA   │
└───────────┴───────────┴─────────┘

上面列出的是其中一些导航设施。

$ wc -l openap/data/nav/nav.dat # 26775

$ echo "import pandas as pd; print(
            pd.read_fwf('openap/data/nav/nav.dat',
                        skiprows=3,
                        header=None,
                        encoding='unicode_escape')
              .to_csv(index=False))" \
    | python3 \
    | ~/duckdb \
        -c '.maxwidth 150' \
        -c "SELECT * EXCLUDE(column9)
            FROM   READ_CSV('/dev/stdin')
            WHERE  column1 BETWEEN 57 AND 59
            AND    column2 BETWEEN 21 AND 27
            LIMIT 20"
┌─────────┬───────────┬───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────────────────┐
│ column0 │  column1  │  column2  │ column3 │ column4 │ column5 │ column6 │ column7 │      column8       │
│  int64  │  double   │  double   │ varchar │ double  │ double  │ double  │ varchar │      varchar       │
├─────────┼───────────┼───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────────────────┤
│       2 │ 58.957117 │ 22.872158 │ 0       │   317.0 │    80.0 │     0.0 │ OZ      │ KARDLA NDB         │
│       2 │ 58.270722 │ 22.508778 │ 0       │   350.0 │    80.0 │     0.0 │ WA      │ KURESSAARE NDB     │
│       2 │ 58.490806 │ 24.571556 │ 0       │   425.0 │    80.0 │     0.0 │ RC      │ PARNU NDB          │
│       2 │ 58.435833 │ 24.495861 │ 0       │   376.0 │    25.0 │     0.0 │ R       │ PARNU NDB          │
│       2 │ 58.308583 │ 26.768417 │ 0       │   397.0 │    80.0 │     0.0 │ UM      │ TARTU NDB          │
│       3 │ 58.228333 │ 22.515361 │ 39      │   240.0 │    50.0 │     3.0 │ KRS     │ KURESSAARE VOR-DME │
│       3 │ 58.416583 │ 24.465972 │ 58      │   590.0 │    25.0 │     6.0 │ PRN     │ PARNU VOR-DME      │
│       3 │ 57.366944 │ 21.556222 │ 0       │   360.0 │   130.0 │     5.3 │ VNT     │ VENTSPILS VOR-DME  │
│       3 │ 58.655889 │ 25.574778 │ 227     │   490.0 │    80.0 │     5.0 │ VI      │ VOHMA VOR-DME      │
│       1 │ 58.228333 │ 22.515361 │ 3       │   124.0 │     5.0 │     0.0 │ KR      │ KURESSAARE VOR-DME │
│       1 │ 58.416583 │ 24.465972 │ 5       │   159.0 │     2.0 │     0.0 │ PR      │ PARNU VOR-DME      │
│       1 │ 57.366944 │ 21.556222 │ NULL    │   136.0 │    13.0 │     0.0 │ VN      │ VENTSPILS VOR-DME  │
│       1 │ 58.655889 │ 25.574778 │ 22      │   149.0 │     8.0 │     0.0 │ VI      │ VOHMA VOR-DME      │
│       1 │ 58.992083 │ 22.830972 │ 3       │   176.0 │     2.0 │     0.0 │ KR      │ KARDLA DME         │
└─────────┴───────────┴───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────────────────┘

从图卢兹到柏林

接下来,我将找出一条从图卢兹-布拉尼亚克机场(LFBO / TLS)飞往柏林勃兰登堡机场(EDDB / BER)的最优航线。

$ python3
import numpy as np
from   openap.aero import cas2mach, ft, kts
from   openap.extra.nav import airport
from   pygeodesy.ellipsoidalVincenty import LatLon

from skdecide.hub.domain\
        .flight_planning\
        .aircraft_performance\
        .bean.aircraft_state \
    import AircraftState

from skdecide.hub.domain\
        .flight_planning\
        .aircraft_performance\
        .performance.performance_model_enum \
    import PerformanceModelEnum

from skdecide.hub.domain\
        .flight_planning\
        .aircraft_performance\
        .performance.phase_enum \
    import PhaseEnum

from skdecide.hub.domain\
        .flight_planning\
        .aircraft_performance\
        .performance.rating_enum \
    import RatingEnum

from skdecide.hub.domain\
        .flight_planning\
        .domain \
    import FlightPlanningDomain, \
           WeatherDate

from skdecide.hub.domain\
        .flight_planning\
        .flightplanning_utils \
    import plot_network_adapted

from skdecide.hub.solver.astar import Astar

启发式参数可取 "time"、"distance"、"lazy_fuel"、"lazy_time" 或 None。若未传入参数,A* 将采用类似 Dijkstra 的搜索算法。

origin        = "LFPG"
destination   = "LFBO"
aircraft      = "A320"
weather_date  = WeatherDate(day=1, month=5, year=2026)
heuristic     = "lazy_fuel"
cost_function = "fuel"

acState = AircraftState(
    model_type="A320",
    performance_model_type=PerformanceModelEnum.OPENAP,
    gw_kg=80_000,
    zp_ft=10_000,
    mach=cas2mach(250 * kts, h=10_000 * ft),
    phase=PhaseEnum.CLIMB,
    rating_level=RatingEnum.MCL,
    cg=0.3)

domain_factory = lambda: FlightPlanningDomain(
    aircraft_state=acState,
    mach_cruise=0.78,
    mach_climb=0.7,
    mach_descent=0.65,
    nb_forward_points=20,
    nb_lateral_points=10,
    nb_climb_descent_steps=5,
    flight_levels_ft=list(np.arange(30_000, 38_000 + 2_000, 2_000)),
    graph_width="medium",
    origin=LatLon(43.629444, 1.363056),
    destination="EDDB",
    objective=cost_function,
    heuristic_name=heuristic,
    weather_date=weather_date)

domain = domain_factory()

运行上述代码时,如果尚未从 NOAA 获取天气数据且航班日期在过往六个月内,系统将下载 GRB2 文件。

$ du -hs ~/skdecide_data/weather/grib/nowcast/*/*.grb2

每个文件都包含全球范围的数据。以下是在 QGIS 中以地球形式渲染的 gfs_4_20260501_1800_000.grb2 内容:

这是求解器的海拔高度和地理位置搜索空间:

这是求解器计算出的最优飞行路径:

A* 在 0.28 秒内完成了计算。最终结果如下:

为了让飞行计划的每一步都更易读,我会将其格式化:

144M    /home/mark/skdecide_data/weather/grib/nowcast/20260501/gfs_4_20260501_0000_000.grb2
144M    /home/mark/skdecide_data/weather/grib/nowcast/20260501/gfs_4_20260501_0600_000.grb2
143M    /home/mark/skdecide_data/weather/grib/nowcast/20260501/gfs_4_20260501_1200_000.grb2
143M    /home/mark/skdecide_data/weather/grib/nowcast/20260501/gfs_4_20260501_1800_000.grb2
Flight Planning
plot_network_adapted(
    graph=domain.network,
    p0=LatLon(43.629444, 1.363056),
    p1=LatLon(
        airport("EDDB")["lat"],
        airport("EDDB")["lon"],
        airport("EDDB")["alt"] * ft))
solver = Astar(
            domain_factory=domain_factory,
            heuristic=lambda d, s: d.heuristic(s),
            parallel=False)

solver.solve()
A* finished to solve from state ... in 0.28 seconds
domain.custom_rollout(solver=solver, make_img=True)
Flight Planning
Goal reached after 19 steps!
({'time': 7666.281474928903, 'fuel': 5855.093906205222}, None)
domain.observation.trajectory.to_csv('TLS-BER.csv', index=None)
$ ~/duckdb
SELECT   phase: UPPER(phase),
         time_: ts::INT,
         alt:   alt::INT,
         mass:  mass::INT,
         mach:  ROUND(mach, 2),
         cas:   cas::INT,
         fuel:  fuel::INT,
         geom:  ST_POINT(lon, lat)
FROM     'TLS-BER.csv'
ORDER BY ts;
┌─────────┬───────┬───────┬───────┬────────┬───────┬───────┬────────────────────────────────────────────────┐
│  phase  │ time_ │  alt  │ mass  │  mach  │  cas  │ fuel  │                      geom                      │
│ varchar │ int32 │ int32 │ int32 │ double │ int32 │ int32 │                    geometry                    │
├─────────┼───────┼───────┼───────┼────────┼───────┼───────┼────────────────────────────────────────────────┤
│ CLIMB   │ 28800 │     0 │ 80000 │   0.45 │   154 │     0 │ POINT (1.363056 43.629444)                     │
│ CLIMB   │ 29183 │ 12000 │ 79402 │    0.7 │   194 │   598 │ POINT (1.3614644301412264 44.431571122861556)  │
│ CLIMB   │ 29767 │ 18000 │ 78717 │    0.7 │   173 │   685 │ POINT (0.8028803270337778 45.54329961845038)   │
│ CLIMB   │ 30367 │ 24000 │ 78104 │    0.7 │   154 │     2 │ POINT (0.22308186850312028 46.64860136322122)  │
│ CLIMB   │ 30369 │ 24000 │ 78102 │    0.7 │   154 │     2 │ POINT (0.2213600223765711 46.65181397470445)   │
│ CLIMB   │ 30691 │ 30000 │ 77808 │    0.7 │   135 │   294 │ POINT (0.781910701674247 47.146798229437145)   │
│ CRUISE  │ 31222 │ 30000 │ 77331 │   0.78 │   152 │   477 │ POINT (0.18057649919536045 48.254448220756515) │
│ CRUISE  │ 31515 │ 30000 │ 77070 │   0.78 │   152 │   262 │ POINT (0.7571242215277763 48.74964861278412)   │
│ CRUISE  │ 31805 │ 30000 │ 76812 │   0.78 │   152 │   258 │ POINT (1.3446668642885302 49.24219441171162)   │
│ CRUISE  │ 32093 │ 30000 │ 76555 │   0.78 │   152 │   256 │ POINT (1.943586016571517 49.732000774167815)   │
│ CRUISE  │ 32382 │ 30000 │ 76298 │   0.78 │   152 │   257 │ POINT (2.554285524033502 50.218985517421046)   │
│ CRUISE  │ 32672 │ 30000 │ 76041 │   0.78 │   152 │   257 │ POINT (3.1771982015706532 50.70307279893911)   │
│ CRUISE  │ 32961 │ 30000 │ 75786 │   0.78 │   152 │   256 │ POINT (3.812797488711077 51.18419969012497)    │
│ CRUISE  │ 33252 │ 30000 │ 75529 │   0.78 │   152 │   257 │ POINT (4.461619033936127 51.66232853895605)    │
│ CRUISE  │ 33547 │ 32000 │ 75270 │   0.78 │   146 │   259 │ POINT (5.1243038676232 52.13747186609129)      │
│ CRUISE  │ 34103 │ 30000 │ 74794 │   0.78 │   152 │   476 │ POINT (7.006628090474678 51.93831668841654)    │
│ DESCENT │ 34458 │ 24031 │ 74503 │   0.65 │   142 │   290 │ POINT (7.7011825929472675 52.40044729715865)   │
│ DESCENT │ 35058 │ 18063 │ 73995 │   0.65 │   160 │    51 │ POINT (9.423199233939213 52.1821111144618)     │
│ DESCENT │ 35114 │ 18063 │ 73944 │   0.65 │   160 │    51 │ POINT (9.580613666598857 52.160789987133924)   │
│ DESCENT │ 35714 │ 12094 │ 73388 │   0.65 │   179 │    13 │ POINT (11.398384342445423 51.895274115132494)  │
│ DESCENT │ 35727 │ 12094 │ 73375 │   0.65 │   179 │    13 │ POINT (11.435080901553494 51.88959204464927)   │
│ DESCENT │ 36063 │  6126 │ 73023 │   0.65 │   199 │   352 │ POINT (12.17769156482302 52.32776873134525)    │
│ DESCENT │ 36466 │    48 │ 72534 │   0.65 │   221 │   488 │ POINT (13.48503 52.36769)                      │
└─────────┴───────┴───────┴───────┴────────┴───────┴───────┴────────────────────────────────────────────────┘

我会将飞行计划导出为 Parquet 格式,并在 QGIS 的地面风数据上渲染出来。

COPY (
    SELECT   * EXCLUDE(lon, lat),
             geometry: ST_POINT(lon, lat)
    FROM     'TLS-BER.csv'
    ORDER BY ts
) TO 'TLS-BER.parquet' (
      FORMAT 'PARQUET',
      CODEC  'ZSTD',
      COMPRESSION_LEVEL 22,
      ROW_GROUP_SIZE 15000);

图卢兹至华沙

下面,我将寻找从图卢兹-布拉尼亚克机场(LFBO / TLS)到华沙肖邦机场(EPWA / WAW)的最优飞行路径。

初始目标高度将比上一个示例高得多。结果是一条能够走得更直的路径。

acState = AircraftState(
    model_type="A320",
    performance_model_type=PerformanceModelEnum.OPENAP,
    gw_kg=80_000,
    zp_ft=18000.0,
    mach=cas2mach(250 * kts, h=10_000 * ft),
    phase=PhaseEnum.CLIMB,
    rating_level=RatingEnum.MCL,
    cg=0.3,
    x_graph=5,
    y_graph=5,
    z_graph=10)

domain_factory = lambda: FlightPlanningDomain(
    aircraft_state=acState,
    mach_cruise=0.78,
    mach_climb=0.7,
    mach_descent=0.65,
    nb_forward_points=20,
    nb_lateral_points=10,
    nb_climb_descent_steps=5,
    flight_levels_ft=list(np.arange(30_000, 38_000 + 2_000, 2_000)),
    graph_width="medium",
    origin=LatLon(43.629444, 1.363056),
    destination="EPWA",
    objective=cost_function,
    heuristic_name=heuristic,
    weather_date=weather_date)

domain = domain_factory()

solver = Astar(
            domain_factory=domain_factory,
            heuristic=lambda d, s: d.heuristic(s),
            parallel=False)

solver.solve()
A* finished to solve from state ... in 29.45 seconds.
domain.custom_rollout(solver=solver, make_img=True)
Goal reached after 14 steps!
({'time': 6153.660431613251, 'fuel': 5600.171145693044}, None)

华沙距离图卢兹比柏林远了 500 公里。但在给定风况下,更快的爬升速率使飞机能够选择一条更直接的路径。这使其比飞柏林的航班早到近 45 分钟,且耗油量仅为后者的 76%。

上述飞行计划包含以下步骤。

domain.observation.trajectory.to_csv('TLS-WAW.csv', index=None)
$ ~/duckdb
SELECT   phase: UPPER(phase),
         time_: ts::INT,
         alt:   alt::INT,
         mass:  mass::INT,
         mach:  ROUND(mach, 2),
         cas:   cas::INT,
         fuel:  fuel::INT,
         geom:  ST_POINT(lon, lat)
FROM     'TLS-WAW.csv'
ORDER BY ts;
┌─────────┬───────┬───────┬───────┬────────┬───────┬───────┬───────────────────────────────────────────────┐
│  phase  │ time_ │  alt  │ mass  │  mach  │  cas  │ fuel  │                     geom                      │
│ varchar │ int32 │ int32 │ int32 │ double │ int32 │ int32 │                   geometry                    │
├─────────┼───────┼───────┼───────┼────────┼───────┼───────┼───────────────────────────────────────────────┤
│ CLIMB   │ 28800 │ 30000 │ 80000 │   0.45 │    85 │     0 │ POINT (6.321780309765473 45.78957852956533)   │
│ CRUISE  │ 29196 │ 32000 │ 79639 │   0.78 │   146 │   361 │ POINT (7.274006698354081 46.275553205520794)  │
│ CRUISE  │ 29604 │ 34000 │ 79276 │   0.78 │   139 │   363 │ POINT (8.243146568075773 46.75332543362598)   │
│ CRUISE  │ 30018 │ 36000 │ 78915 │   0.78 │   133 │   361 │ POINT (9.229481862337197 47.22261290149568)   │
│ CRUISE  │ 30436 │ 38000 │ 78555 │   0.78 │   127 │   360 │ POINT (10.23327610333648 47.68312559281413)   │
│ CRUISE  │ 30861 │ 38000 │ 78191 │   0.78 │   127 │   365 │ POINT (11.25477083484032 48.13456566890824)   │
│ CRUISE  │ 31297 │ 36000 │ 77819 │   0.78 │   133 │   371 │ POINT (12.29418112454121 48.57662718213212)   │
│ CRUISE  │ 31732 │ 34000 │ 77449 │   0.78 │   139 │   370 │ POINT (13.351689351409924 49.00899540355703)  │
│ CRUISE  │ 32161 │ 32000 │ 77082 │   0.78 │   146 │   367 │ POINT (14.427435465032865 49.431345252175966) │
│ CRUISE  │ 32589 │ 30000 │ 76710 │   0.78 │   152 │   372 │ POINT (15.521498988740307 49.843337488922174) │
│ DESCENT │ 33101 │ 24066 │ 76280 │   0.65 │   142 │   429 │ POINT (16.633858546975723 50.2446086742223)   │
│ DESCENT │ 33589 │ 18131 │ 75861 │   0.65 │   160 │   420 │ POINT (17.764276668345886 50.63474030548783)  │
│ DESCENT │ 34046 │ 12197 │ 75433 │   0.65 │   179 │   428 │ POINT (18.91184798374883 51.01313503252613)   │
│ DESCENT │ 34472 │  6262 │ 74984 │   0.65 │   199 │   449 │ POINT (20.071843186874247 51.378167839584854) │
│ DESCENT │ 34954 │   100 │ 74400 │   0.65 │   221 │   584 │ POINT (20.94663 52.17147)                     │
└─────────┴───────┴───────┴───────┴────────┴───────┴───────┴───────────────────────────────────────────────┘

Airbus A320 与 Boeing 737 的对比

OpenTop 可以和 OpenAP 搭配使用,用来推算两个机场之间的飞行轨迹。

它的优化器需要一个网格代价文件。我先下载项目提供的一个 142 MB 的 NetCDF 示例文件。

$ wget https://opendap.4tu.nl/thredds/fileServer/data2/djht/bea8a3fe-e34c-4598-9f94-c5a5c63348e5/1/contrail_original.nc

成本文件可以是 Casadi 或 Parquet 格式。我参考了文档中的示例,它生成了一个 246 KB 的 Casadi 文件。

import openap
import pandas as pd
from scipy.ndimage import gaussian_filter
from opentop.tools import cached_interpolant_from_dataframe
import xarray as xr

ds = xr.open_dataset('contrail_original.nc')\
       .sel(time='2015-12-18')

level_pressure = [
    0.0000,
    10.0000,
    30.0000,
    50.0000,
    70.0000,
    90.0787,
    110.6606,
    132.3968,
    155.7909,
    181.1544,
    208.6494,
    238.3258,
    270.1530,
    304.0465,
    339.8891,
    377.5467,
    416.8789,
    457.7442,
    500.0000,
    543.4970,
    588.0685,
    633.5144,
    679.5799,
    725.9285,
    772.1102,
    817.5241,
    861.3757,
    902.6287,
    939.9520,
    971.6610,
    995.6532,
    1009.3396]

df = (
    ds.to_dataframe()
    .reset_index()
    .assign(lev=lambda x: x.lev.astype(int))
    .merge(
        pd.DataFrame(level_pressure, columns=["hPa"]).reset_index(names="lev"),
        on="lev",
    )
    .assign(height=lambda x: openap.aero.h_isa(x.hPa * 100).round(-2))
    .assign(longitude=lambda x: ((x.lon + 180) % 360 - 180))
    .query("height<15000"))

df_cost_world = df.rename(
    columns={
        "lat": "latitude",
        "atr20_contrail": "cost",
    }
)[["time",
   "latitude",
   "longitude",
   "hPa",
   "height",
   "cost"]]

df_cost = df_cost_world.query(
    "-20<longitude<40 and 30<latitude<70 and time.dt.hour==12"
).sort_values(["height", "latitude", "longitude"])

cost = df_cost.cost.values.reshape(
    df_cost.height.nunique(),
    df_cost.latitude.nunique(),
    df_cost.longitude.nunique())

cost_ = gaussian_filter(cost, sigma=1, mode="nearest")
df_cost = df_cost.assign(cost=cost_.flatten())

interpolant = cached_interpolant_from_dataframe(
                df_cost,
                "contrail.casadi",
                shape="bspline")

以下是其内容的开头和结尾几个字节。

$ hexdump -C contrail.casadi | head
00000000  6a 68 70 6e 6e 61 67 69  69 65 61 68 61 61 61 61  |jhpnnagiieahaaaa|
00000010  64 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |daaaaaaaaaaaaaaa|
00000020  61 61 66 61 65 67 61 61  6c 61 61 61 61 61 61 61  |aafaegaalaaaaaaa|
00000030  6a 65 6f 67 65 68 66 67  63 68 61 68 70 67 6d 67  |jeogehfgchahpgmg|
00000040  62 67 6f 67 65 68 68 61  61 61 61 61 61 61 63 67  |bgogehhaaaaaaacg|
00000050  64 68 61 68 6d 67 6a 67  6f 67 66 67 63 61 61 61  |dhahmgjgogfgcaaa|
00000060  61 61 61 61 6a 61 61 61  61 61 61 61 68 67 63 68  |aaaajaaaaaaahgch|
00000070  6a 67 65 67 70 66 64 67  70 67 64 68 65 68 61 61  |jgegpfdgpgdhehaa|
00000080  61 61 61 61 61 61 62 61  69 61 61 61 61 61 61 61  |aaaaaabaiaaaaaaa|
00000090  62 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |baaaaaaaaaaaaaaa|
$ hexdump -C contrail.casadi | tail
0003d620  61 61 61 61 61 61 64 62  61 61 61 61 61 61 61 61  |aaaaaadbaaaaaaaa|
0003d630  61 61 61 61 61 61 67 62  61 61 61 61 61 61 61 61  |aaaaaagbaaaaaaaa|
0003d640  61 61 61 61 61 61 68 62  61 61 61 61 61 61 61 61  |aaaaaahbaaaaaaaa|
0003d650  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
0003d660  61 61 61 61 61 61 62 61  61 61 61 61 61 61 61 61  |aaaaaabaaaaaaaaa|
0003d670  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
0003d680  61 61 61 61 61 61 62 61  61 61 61 62 61 61 61 61  |aaaaaabaaabaaaaa|
0003d690  61 61 61 61 61 61 61 61  61 61 63 68 67 61 61 61  |aaaaaaaaaachgaaa|
0003d6a0  61 61 61 61 61 61 61 61  61 61 61 61              |aaaaaaaaaaaa|
0003d6ac

我注意到文件内容重复性很高,压缩效果应该不错。

$ gzip -9 < contrail.casadi | wc -c
90949

接下来获取从阿姆斯特丹史基浦机场(EHAM / AMS)飞往法兰克福(EDDF / FRA)的 A320 最优飞行指标。

$ opentop optimize \
    EHAM EDDF \
    -a A320 \
    --phase all \
    --obj "0.3*fuel+0.7*grid" \
    --grid contrail.casadi
aircraft:  A320
route:     EHAM → EDDF
phase:     all
objective: 0.3*fuel+0.7*grid
m0:        0.85
max_iter:  1500
grid file: contrail.casadi

success:       True
return_status: Solve_Succeeded
iter_count:    179
wall time:     12.2 s
objective:     4.8768e+02
fuel burn:     1625.6 kg
max altitude:  19891 ft
flight time:   35.8 min

然后用波音 737 做同样的计算。

$ opentop optimize \
    EHAM EDDF \
    -a B737 \
    --phase all \
    --obj "0.3*fuel+0.7*grid" \
    --grid contrail.casadi
机型:      B737
航线:      EHAM → EDDF
飞行阶段:  all
目标函数:  0.3*fuel+0.7*grid
m0:        0.85
最大迭代:  1500
网格文件:  contrail.casadi

求解成功:     True
返回状态:     Solve_Succeeded
迭代次数:     141
耗时:         9.9 秒
目标值:       4.8662e+02
燃油消耗:     1622.1 kg
最大高度:     21968 ft
飞行时间:     39.2 分钟
感谢你抽空阅读本文。我为北美和欧洲的客户提供咨询与开发服务。如果你想了解这些服务如何帮助你的业务,欢迎通过 LinkedIn 联系我。
原始来源: Hacker News

评论 (0)