pycontrails 0.50.2__cp310-cp310-win_amd64.whl → 0.51.1__cp310-cp310-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pycontrails might be problematic. Click here for more details.

Files changed (32) hide show
  1. pycontrails/_version.py +2 -2
  2. pycontrails/core/datalib.py +22 -0
  3. pycontrails/core/flight.py +87 -7
  4. pycontrails/core/met.py +33 -5
  5. pycontrails/core/polygon.py +10 -3
  6. pycontrails/core/rgi_cython.cp310-win_amd64.pyd +0 -0
  7. pycontrails/datalib/ecmwf/__init__.py +6 -0
  8. pycontrails/datalib/ecmwf/arco_era5.py +2 -53
  9. pycontrails/datalib/ecmwf/common.py +4 -0
  10. pycontrails/datalib/ecmwf/era5.py +2 -6
  11. pycontrails/datalib/ecmwf/era5_model_level.py +481 -0
  12. pycontrails/datalib/ecmwf/hres_model_level.py +494 -0
  13. pycontrails/datalib/ecmwf/model_levels.py +79 -0
  14. pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv +139 -0
  15. pycontrails/datalib/ecmwf/variables.py +12 -0
  16. pycontrails/models/humidity_scaling/humidity_scaling.py +55 -8
  17. pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq +0 -0
  18. pycontrails/models/ps_model/ps_aircraft_params.py +19 -3
  19. pycontrails/models/ps_model/ps_grid.py +21 -21
  20. pycontrails/models/ps_model/ps_model.py +41 -6
  21. pycontrails/models/ps_model/ps_operational_limits.py +15 -6
  22. pycontrails/models/ps_model/static/{ps-aircraft-params-20240417.csv → ps-aircraft-params-20240524.csv} +16 -12
  23. pycontrails/models/ps_model/static/ps-synonym-list-20240524.csv +103 -0
  24. pycontrails/physics/thermo.py +1 -1
  25. pycontrails/utils/types.py +3 -2
  26. {pycontrails-0.50.2.dist-info → pycontrails-0.51.1.dist-info}/METADATA +4 -4
  27. {pycontrails-0.50.2.dist-info → pycontrails-0.51.1.dist-info}/RECORD +32 -26
  28. /pycontrails/models/humidity_scaling/quantiles/{era5-quantiles.pq → era5-pressure-level-quantiles.pq} +0 -0
  29. {pycontrails-0.50.2.dist-info → pycontrails-0.51.1.dist-info}/LICENSE +0 -0
  30. {pycontrails-0.50.2.dist-info → pycontrails-0.51.1.dist-info}/NOTICE +0 -0
  31. {pycontrails-0.50.2.dist-info → pycontrails-0.51.1.dist-info}/WHEEL +0 -0
  32. {pycontrails-0.50.2.dist-info → pycontrails-0.51.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,139 @@
1
+ n,a [Pa],b,ph [hPa],pf [hPa],Geopotential Altitude [m],Geometric Altitude [m],Temperature [K],Density [kg/m^3]
2
+ 0,0.0,0.0,0.0,,,,,
3
+ 1,2.000365,0.0,0.02,0.01,79301.79,80301.65,198.05,1.8e-05
4
+ 2,3.102241,0.0,0.031,0.0255,73721.58,74584.91,209.21,4.2e-05
5
+ 3,4.666084,0.0,0.0467,0.0388,71115.75,71918.79,214.42,6.3e-05
6
+ 4,6.827977,0.0,0.0683,0.0575,68618.43,69365.77,221.32,9e-05
7
+ 5,9.746966,0.0,0.0975,0.0829,66210.99,66906.53,228.06,0.000127
8
+ 6,13.605424,0.0,0.1361,0.1168,63890.03,64537.43,234.56,0.000173
9
+ 7,18.608931,0.0,0.1861,0.1611,61651.77,62254.39,240.83,0.000233
10
+ 8,24.985718,0.0,0.2499,0.218,59492.5,60053.46,246.87,0.000308
11
+ 9,32.98571,0.0,0.3299,0.2899,57408.61,57930.78,252.71,0.0004
12
+ 10,42.879242,0.0,0.4288,0.3793,55396.62,55882.68,258.34,0.000512
13
+ 11,54.955463,0.0,0.5496,0.4892,53453.2,53905.62,263.78,0.000646
14
+ 12,69.520576,0.0,0.6952,0.6224,51575.15,51996.21,269.04,0.000806
15
+ 13,86.895882,0.0,0.869,0.7821,49767.41,50159.36,270.65,0.001007
16
+ 14,107.415741,0.0,1.0742,0.9716,48048.7,48413.94,270.65,0.001251
17
+ 15,131.425507,0.0,1.3143,1.1942,46416.22,46756.98,269.02,0.001546
18
+ 16,159.279404,0.0,1.5928,1.4535,44881.17,45199.69,264.72,0.001913
19
+ 17,191.338562,0.0,1.9134,1.7531,43440.23,43738.55,260.68,0.002343
20
+ 18,227.968948,0.0,2.2797,2.0965,42085.0,42364.93,256.89,0.002843
21
+ 19,269.539581,0.0,2.6954,2.4875,40808.05,41071.2,253.31,0.003421
22
+ 20,316.420746,0.0,3.1642,2.9298,39602.76,39850.56,249.94,0.004084
23
+ 21,368.982361,0.0,3.6898,3.427,38463.25,38696.94,246.75,0.004838
24
+ 22,427.592499,0.0,4.2759,3.9829,37384.22,37604.95,243.73,0.005693
25
+ 23,492.616028,0.0,4.9262,4.601,36360.94,36569.72,240.86,0.006655
26
+ 24,564.413452,0.0,5.6441,5.2851,35389.15,35586.89,238.14,0.007731
27
+ 25,643.339905,0.0,6.4334,6.0388,34465.0,34652.52,235.55,0.008931
28
+ 26,729.744141,0.0,7.2974,6.8654,33585.02,33763.05,233.09,0.010261
29
+ 27,823.967834,0.0,8.2397,7.7686,32746.04,32915.27,230.74,0.011729
30
+ 28,926.34491,0.0,9.2634,8.7516,31945.53,32106.57,228.6,0.013337
31
+ 29,1037.201172,0.0,10.372,9.8177,31177.59,31330.96,227.83,0.015012
32
+ 30,1156.853638,0.0,11.5685,10.9703,30438.54,30584.71,227.09,0.016829
33
+ 31,1285.610352,0.0,12.8561,12.2123,29726.69,29866.09,226.38,0.018793
34
+ 32,1423.770142,0.0,14.2377,13.5469,29040.48,29173.5,225.69,0.02091
35
+ 33,1571.622925,0.0,15.7162,14.977,28378.46,28505.47,225.03,0.023186
36
+ 34,1729.448975,0.0,17.2945,16.5054,27739.29,27860.64,224.39,0.025624
37
+ 35,1897.519287,0.0,18.9752,18.1348,27121.74,27237.73,223.77,0.028232
38
+ 36,2076.095947,0.0,20.761,19.8681,26524.63,26635.56,223.17,0.031013
39
+ 37,2265.431641,0.0,22.6543,21.7076,25946.9,26053.04,222.6,0.033972
40
+ 38,2465.770508,0.0,24.6577,23.656,25387.55,25489.15,222.04,0.037115
41
+ 39,2677.348145,0.0,26.7735,25.7156,24845.63,24942.93,221.5,0.040445
42
+ 40,2900.391357,0.0,29.0039,27.8887,24320.28,24413.5,220.97,0.043967
43
+ 41,3135.119385,0.0,31.3512,30.1776,23810.67,23900.02,220.46,0.047685
44
+ 42,3381.743652,0.0,33.8174,32.5843,23316.04,23401.71,219.97,0.051604
45
+ 43,3640.468262,0.0,36.4047,35.1111,22835.68,22917.85,219.49,0.055727
46
+ 44,3911.490479,0.0,39.1149,37.7598,22368.91,22447.75,219.02,0.060059
47
+ 45,4194.930664,0.0,41.9493,40.5321,21915.16,21990.82,218.57,0.064602
48
+ 46,4490.817383,0.0,44.9082,43.4287,21473.98,21546.62,218.12,0.069359
49
+ 47,4799.149414,0.0,47.9915,46.4498,21045.0,21114.77,217.7,0.07433
50
+ 48,5119.89502,0.0,51.199,49.5952,20627.87,20694.9,217.28,0.079516
51
+ 49,5452.990723,0.0,54.5299,52.8644,20222.24,20286.66,216.87,0.084916
52
+ 50,5798.344727,0.0,57.9834,56.2567,19827.95,19889.88,216.65,0.090458
53
+ 51,6156.074219,0.0,61.5607,59.7721,19443.55,19503.09,216.65,0.09611
54
+ 52,6526.946777,0.0,65.2695,63.4151,19068.35,19125.61,216.65,0.101968
55
+ 53,6911.870605,0.0,69.1187,67.1941,18701.27,18756.34,216.65,0.108045
56
+ 54,7311.869141,0.0,73.1187,71.1187,18341.27,18394.25,216.65,0.114355
57
+ 55,7727.412109,7e-06,77.281,75.1999,17987.41,18038.35,216.65,0.120917
58
+ 56,8159.354004,2.4e-05,81.6182,79.4496,17638.78,17687.77,216.65,0.127751
59
+ 57,8608.525391,5.9e-05,86.145,83.8816,17294.53,17341.62,216.65,0.134877
60
+ 58,9076.400391,0.000112,90.8774,88.5112,16953.83,16999.08,216.65,0.142321
61
+ 59,9562.682617,0.000199,95.828,93.3527,16616.09,16659.55,216.65,0.150106
62
+ 60,10065.978516,0.00034,101.0047,98.4164,16281.1,16322.83,216.65,0.158248
63
+ 61,10584.631836,0.000562,106.4153,103.71,15948.85,15988.88,216.65,0.16676
64
+ 62,11116.662109,0.00089,112.0681,109.2417,15619.3,15657.7,216.65,0.175655
65
+ 63,11660.067383,0.001353,117.9714,115.0198,15292.44,15329.24,216.65,0.184946
66
+ 64,12211.547852,0.001992,124.1337,121.0526,14968.24,15003.5,216.65,0.194646
67
+ 65,12766.873047,0.002857,130.5637,127.3487,14646.68,14680.44,216.65,0.20477
68
+ 66,13324.668945,0.003971,137.2703,133.917,14327.75,14360.05,216.65,0.215331
69
+ 67,13881.331055,0.005378,144.2624,140.7663,14011.41,14042.3,216.65,0.226345
70
+ 68,14432.139648,0.007133,151.5493,147.9058,13697.65,13727.18,216.65,0.237825
71
+ 69,14975.615234,0.009261,159.1403,155.3448,13386.45,13414.65,216.65,0.249786
72
+ 70,15508.256836,0.011806,167.045,163.0927,13077.79,13104.7,216.65,0.262244
73
+ 71,16026.115234,0.014816,175.2731,171.1591,12771.64,12797.3,216.65,0.275215
74
+ 72,16527.322266,0.018318,183.8344,179.5537,12467.99,12492.44,216.65,0.288713
75
+ 73,17008.789063,0.022355,192.7389,188.2867,12166.81,12190.1,216.65,0.302755
76
+ 74,17467.613281,0.026964,201.9969,197.3679,11868.08,11890.24,216.65,0.317357
77
+ 75,17901.621094,0.032176,211.6186,206.8078,11571.79,11592.86,216.65,0.332536
78
+ 76,18308.433594,0.038026,221.6146,216.6166,11277.92,11297.93,216.65,0.348308
79
+ 77,18685.71875,0.044548,231.9954,226.805,10986.7,11005.69,216.74,0.364545
80
+ 78,19031.289063,0.051773,242.7719,237.3837,10696.22,10714.22,218.62,0.378253
81
+ 79,19343.511719,0.059728,253.9549,248.3634,10405.61,10422.64,220.51,0.392358
82
+ 80,19620.042969,0.068448,265.5556,259.7553,10114.89,10130.98,222.4,0.406868
83
+ 81,19859.390625,0.077958,277.5852,271.5704,9824.08,9839.26,224.29,0.42179
84
+ 82,20059.931641,0.088286,290.0548,283.82,9533.2,9547.49,226.18,0.43713
85
+ 83,20219.664063,0.099462,302.9762,296.5155,9242.26,9255.7,228.08,0.452897
86
+ 84,20337.863281,0.111505,316.3607,309.6684,8951.3,8963.9,229.97,0.469097
87
+ 85,20412.308594,0.124448,330.2202,323.2904,8660.32,8672.11,231.86,0.485737
88
+ 86,20442.078125,0.138313,344.5663,337.3932,8369.35,8380.36,233.75,0.502825
89
+ 87,20425.71875,0.153125,359.4111,351.9887,8078.41,8088.67,235.64,0.520367
90
+ 88,20361.816406,0.16891,374.7666,367.0889,7787.51,7797.04,237.53,0.53837
91
+ 89,20249.511719,0.185689,390.645,382.7058,7496.68,7505.51,239.42,0.556842
92
+ 90,20087.085938,0.203491,407.0583,398.8516,7205.93,7214.09,241.31,0.57579
93
+ 91,19874.025391,0.222333,424.019,415.5387,6915.29,6922.8,243.2,0.595219
94
+ 92,19608.572266,0.242244,441.5395,432.7792,6624.76,6631.66,245.09,0.615138
95
+ 93,19290.226563,0.263242,459.6321,450.5858,6334.38,6340.68,246.98,0.635553
96
+ 94,18917.460938,0.285354,478.3096,468.9708,6044.15,6049.89,248.86,0.656471
97
+ 95,18489.707031,0.308598,497.5845,487.947,5754.1,5759.3,250.75,0.677899
98
+ 96,18006.925781,0.332939,517.4198,507.5021,5464.6,5469.3,252.63,0.699815
99
+ 97,17471.839844,0.358254,537.7195,527.5696,5176.77,5180.98,254.5,0.722139
100
+ 98,16888.6875,0.384363,558.343,548.0312,4892.26,4896.02,256.35,0.744735
101
+ 99,16262.046875,0.411125,579.1926,568.7678,4612.58,4615.92,258.17,0.767472
102
+ 100,15596.695313,0.438391,600.1668,589.6797,4338.77,4341.73,259.95,0.790242
103
+ 101,14898.453125,0.466003,621.1624,610.6646,4071.8,4074.41,261.68,0.812937
104
+ 102,14173.324219,0.4938,642.0764,631.6194,3812.53,3814.82,263.37,0.835453
105
+ 103,13427.769531,0.521619,662.8084,652.4424,3561.7,3563.69,265.0,0.857686
106
+ 104,12668.257813,0.549301,683.262,673.0352,3319.94,3321.67,266.57,0.879541
107
+ 105,11901.339844,0.576692,703.3467,693.3043,3087.75,3089.25,268.08,0.900929
108
+ 106,11133.304688,0.603648,722.9795,713.1631,2865.54,2866.83,269.52,0.921768
109
+ 107,10370.175781,0.630036,742.0855,732.5325,2653.58,2654.69,270.9,0.941988
110
+ 108,9617.515625,0.655736,760.5996,751.3426,2452.04,2452.99,272.21,0.961527
111
+ 109,8880.453125,0.680643,778.4661,769.5329,2260.99,2261.8,273.45,0.980334
112
+ 110,8163.375,0.704669,795.6396,787.0528,2080.41,2081.09,274.63,0.998368
113
+ 111,7470.34375,0.727739,812.0847,803.8622,1910.19,1910.76,275.73,1.015598
114
+ 112,6804.421875,0.749797,827.7756,819.9302,1750.14,1750.63,276.77,1.032005
115
+ 113,6168.53125,0.770798,842.6959,835.2358,1600.04,1600.44,277.75,1.047576
116
+ 114,5564.382813,0.790717,856.8376,849.7668,1459.58,1459.91,278.66,1.06231
117
+ 115,4993.796875,0.809536,870.2004,863.519,1328.43,1328.7,279.52,1.076209
118
+ 116,4457.375,0.827256,882.791,876.4957,1206.21,1206.44,280.31,1.089286
119
+ 117,3955.960938,0.843881,894.6222,888.7066,1092.54,1092.73,281.05,1.101558
120
+ 118,3489.234375,0.859432,905.7116,900.1669,987.0,987.15,281.73,1.113047
121
+ 119,3057.265625,0.873929,916.0815,910.8965,889.17,889.29,282.37,1.123777
122
+ 120,2659.140625,0.887408,925.7571,920.9193,798.62,798.72,282.96,1.133779
123
+ 121,2294.242188,0.8999,934.7666,930.2618,714.94,715.02,283.5,1.143084
124
+ 122,1961.5,0.911448,943.1399,938.9532,637.7,637.76,284.0,1.151724
125
+ 123,1659.476563,0.922096,950.9082,947.024,566.49,566.54,284.47,1.159733
126
+ 124,1387.546875,0.931881,958.1037,954.5059,500.91,500.95,284.89,1.167147
127
+ 125,1143.25,0.94086,964.7584,961.4311,440.58,440.61,285.29,1.173999
128
+ 126,926.507813,0.949064,970.9046,967.8315,385.14,385.16,285.65,1.180323
129
+ 127,734.992188,0.95655,976.5737,973.7392,334.22,334.24,285.98,1.186154
130
+ 128,568.0625,0.963352,981.7968,979.1852,287.51,287.52,286.28,1.191523
131
+ 129,424.414063,0.969513,986.6036,984.2002,244.68,244.69,286.56,1.196462
132
+ 130,302.476563,0.975078,991.023,988.8133,205.44,205.44,286.81,1.201001
133
+ 131,202.484375,0.980072,995.0824,993.0527,169.5,169.51,287.05,1.205168
134
+ 132,122.101563,0.984542,998.8081,996.9452,136.62,136.62,287.26,1.208992
135
+ 133,62.78125,0.9885,1002.225,1000.5165,106.54,106.54,287.46,1.212498
136
+ 134,22.835938,0.991984,1005.3562,1003.7906,79.04,79.04,287.64,1.21571
137
+ 135,3.757813,0.995003,1008.2239,1006.79,53.92,53.92,287.8,1.21865
138
+ 136,0.0,0.99763,1010.8487,1009.5363,30.96,30.96,287.95,1.221341
139
+ 137,0.0,1.0,1013.25,1012.0494,10.0,10.0,288.09,1.223803
@@ -240,5 +240,17 @@ SURFACE_VARIABLES = [
240
240
  CloudAreaFraction,
241
241
  SurfaceSolarDownwardRadiation,
242
242
  ]
243
+ MODEL_LEVEL_VARIABLES = [
244
+ met_var.AirTemperature,
245
+ met_var.SpecificHumidity,
246
+ met_var.VerticalVelocity,
247
+ met_var.EastwardWind,
248
+ met_var.NorthwardWind,
249
+ RelativeVorticity,
250
+ Divergence,
251
+ CloudAreaFractionInLayer,
252
+ SpecificCloudIceWaterContent,
253
+ SpecificCloudLiquidWaterContent,
254
+ ]
243
255
 
244
256
  ECMWF_VARIABLES = PRESSURE_LEVEL_VARIABLES + SURFACE_VARIABLES
@@ -591,9 +591,15 @@ class HumidityScalingByLevel(HumidityScaling):
591
591
 
592
592
 
593
593
  @functools.cache
594
- def _load_quantiles() -> pd.DataFrame:
594
+ def _load_quantiles(level_type: str) -> pd.DataFrame:
595
595
  """Load precomputed ERA5 and IAGOS quantiles.
596
596
 
597
+ Parameters
598
+ ----------
599
+ level_type : {"pressure", "model"}
600
+ Select whether to load precomputed quantiles from pressure- vs
601
+ model-level ERA5 data.
602
+
597
603
  Returns
598
604
  -------
599
605
  pd.DataFrame
@@ -602,17 +608,20 @@ def _load_quantiles() -> pd.DataFrame:
602
608
  and the interpolation methodology. The IAOGS quantiles are in the
603
609
  ``("iagos", "iagos")`` column.
604
610
  """
605
- path = pathlib.Path(__file__).parent / "quantiles" / "era5-quantiles.pq"
606
- return pd.read_parquet(path)
611
+ path = pathlib.Path(__file__).parent / "quantiles" / f"era5-{level_type}-level-quantiles.pq"
612
+ df = pd.read_parquet(path)
613
+ df.attrs["path"] = str(path)
614
+ return df
607
615
 
608
616
 
609
617
  def histogram_matching(
610
618
  era5_rhi: ArrayLike,
611
619
  product_type: str,
620
+ level_type: str,
612
621
  member: int | None,
613
622
  q_method: str | None,
614
623
  ) -> npt.NDArray[np.float64]:
615
- """Map ERA5-derived RHi to it's corresponding IAGOS quantile via histogram matching.
624
+ """Map ERA5-derived RHi to its corresponding IAGOS quantile via histogram matching.
616
625
 
617
626
  This matching is performed on a **single** ERA5 ensemble member.
618
627
 
@@ -622,6 +631,11 @@ def histogram_matching(
622
631
  ERA5-derived RHi values for the given ensemble member.
623
632
  product_type : {"reanalysis", "ensemble_members"}
624
633
  The ERA5 product type.
634
+ level_type : {"pressure", "model"}
635
+ Select whether to perform quantile mapping based on quantiles from
636
+ pressure- or model-level ERA5 data. Selecting ``level_type == "model"``
637
+ when ``product_type == "ensemble_members"`` will produce a warning
638
+ and change ``product_type`` to ``"reanalysis"``.
625
639
  member : int | None
626
640
  The ERA5 ensemble member to use. Must be in the range ``[0, 10)``.
627
641
  Only used if ``product_type == "ensemble_members"``.
@@ -634,18 +648,30 @@ def histogram_matching(
634
648
  The IAGOS quantiles corresponding to the ERA5-derived RHi values. Returned
635
649
  as a numpy array with the same shape and dtype as ``era5_rhi``.
636
650
  """
637
- df = _load_quantiles()
651
+ if level_type not in ["pressure", "model"]:
652
+ msg = f"Invalid 'level_type' value '{level_type}'. " "Must be one of ['pressure', 'model']."
653
+ raise ValueError(msg)
654
+ df = _load_quantiles(level_type)
638
655
  iagos_quantiles = df[("iagos", "iagos")]
639
656
 
657
+ if product_type == "ensemble_members" and level_type == "model":
658
+ msg = (
659
+ "No quantiles available for model-level ensemble data. "
660
+ "Switching to product_type = 'reanalysis'."
661
+ )
662
+ warnings.warn(msg)
663
+ product_type = "reanalysis"
664
+
640
665
  if product_type == "ensemble_members":
641
666
  col = f"ensemble{member}", q_method or "linear-q"
642
667
  elif product_type == "reanalysis":
643
668
  col = "reanalysis", q_method or "linear-q"
644
669
  else:
645
- raise ValueError(
670
+ msg = (
646
671
  f"Invalid 'product_type' value '{product_type}'. "
647
672
  "Must be one of ['reanalysis', 'ensemble_members']."
648
673
  )
674
+ raise ValueError(msg)
649
675
 
650
676
  try:
651
677
  era5_quantiles = df[col]
@@ -691,7 +717,7 @@ def histogram_matching_all_members(
691
717
 
692
718
  # Perform histogram matching on the given ensemble member
693
719
  ensemble_member_rhi = histogram_matching(
694
- era5_rhi_all_members[:, member], "ensemble_members", member, q_method
720
+ era5_rhi_all_members[:, member], "ensemble_members", "pressure", member, q_method
695
721
  )
696
722
 
697
723
  # Perform histogram matching on all other ensemble members
@@ -702,7 +728,7 @@ def histogram_matching_all_members(
702
728
  ensemble_mean_rhi += ensemble_member_rhi
703
729
  else:
704
730
  ensemble_mean_rhi += histogram_matching(
705
- era5_rhi_all_members[:, r], "ensemble_members", r, q_method
731
+ era5_rhi_all_members[:, r], "ensemble_members", "pressure", r, q_method
706
732
  )
707
733
 
708
734
  # Divide by the number of ensemble members to get the mean
@@ -766,6 +792,9 @@ class HistogramMatchingParams(models.ModelParams):
766
792
  #: The ERA5 product. Must be one of ``"reanalysis"`` or ``"ensemble_members"``.
767
793
  product_type: str = "reanalysis"
768
794
 
795
+ #: The ERA5 vertical level type. Must be one of ``"pressure"`` or ``"model"``.
796
+ level_type: str = "pressure"
797
+
769
798
  #: The ERA5 ensemble member to use. Must be in the range ``[0, 10)``.
770
799
  #: Only used if ``product_type`` is ``"ensemble_members"``.
771
800
  member: int | None = None
@@ -779,6 +808,23 @@ class HistogramMatching(HumidityScaling):
779
808
  formula = "era5_quantiles -> iagos_quantiles"
780
809
  default_params = HistogramMatchingParams
781
810
 
811
+ def __init__(
812
+ self,
813
+ met: MetDataset | None = None,
814
+ params: dict[str, Any] | None = None,
815
+ **params_kwargs: Any,
816
+ ):
817
+ if (params is None or "level_type" not in params) and (
818
+ params_kwargs is None or "level_type" not in params_kwargs
819
+ ):
820
+ msg = (
821
+ "The default level_type will change from 'pressure' to 'model' "
822
+ "in a future release. To silence this warning, "
823
+ "provide a 'level_type' value when instantiating HistogramMatching."
824
+ )
825
+ warnings.warn(msg, DeprecationWarning)
826
+ super().__init__(met, params, **params_kwargs)
827
+
782
828
  @overrides
783
829
  def scale(
784
830
  self,
@@ -793,6 +839,7 @@ class HistogramMatching(HumidityScaling):
793
839
  rhi_1 = histogram_matching(
794
840
  rhi,
795
841
  self.params["product_type"],
842
+ self.params["level_type"],
796
843
  self.params["member"],
797
844
  self.params["interpolation_q_method"],
798
845
  )
@@ -14,7 +14,7 @@ import pandas as pd
14
14
  from pycontrails.physics import constants as c
15
15
 
16
16
  #: Path to the Poll-Schumann aircraft parameters CSV file.
17
- PS_FILE_PATH = pathlib.Path(__file__).parent / "static" / "ps-aircraft-params-20240417.csv"
17
+ PS_FILE_PATH = pathlib.Path(__file__).parent / "static" / "ps-aircraft-params-20240524.csv"
18
18
 
19
19
 
20
20
  @dataclasses.dataclass(frozen=True)
@@ -192,8 +192,23 @@ def _row_to_aircraft_engine_params(tup: Any) -> tuple[str, PSAircraftEngineParam
192
192
 
193
193
 
194
194
  @functools.cache
195
- def load_aircraft_engine_params() -> Mapping[str, PSAircraftEngineParams]:
196
- """Extract aircraft-engine parameters for each aircraft type supported by the PS model."""
195
+ def load_aircraft_engine_params(
196
+ engine_deterioration_factor: float = 0.025,
197
+ ) -> Mapping[str, PSAircraftEngineParams]:
198
+ """
199
+ Extract aircraft-engine parameters for each aircraft type supported by the PS model.
200
+
201
+ Parameters
202
+ ----------
203
+ engine_deterioration_factor: float
204
+ Account for "in-service" engine deterioration between maintenance cycles.
205
+ Default value reduces `eta_1` by 2.5%, which increases the fuel flow estimates by 2.5%.
206
+
207
+ Returns
208
+ -------
209
+ Mapping[str, PSAircraftEngineParams]
210
+ Aircraft-engine parameters for each aircraft type supported by the PS model.
211
+ """
197
212
  dtypes = {
198
213
  "ICAO": object,
199
214
  "Manufacturer": object,
@@ -239,6 +254,7 @@ def load_aircraft_engine_params() -> Mapping[str, PSAircraftEngineParams]:
239
254
  }
240
255
 
241
256
  df = pd.read_csv(PS_FILE_PATH, dtype=dtypes)
257
+ df["eta_1"] = df["eta_1"] * (1.0 - engine_deterioration_factor)
242
258
 
243
259
  return dict(_row_to_aircraft_engine_params(tup) for tup in df.itertuples(index=False))
244
260
 
@@ -83,7 +83,7 @@ class PSGrid(AircraftPerformanceGrid):
83
83
  from the :attr:`met` attribute of the :class:`PSGrid` instance.
84
84
  The aircraft type is taken from ``source.attrs["aircraft_type"]``. If this field
85
85
  is not present, ``params["aircraft_type"]`` is used instead. See the
86
- static CSV file :file:`ps-aircraft-params-20231117.csv` for a list of supported
86
+ static CSV file :file:`ps-aircraft-params-20240524.csv` for a list of supported
87
87
  aircraft types.
88
88
  **params : Any
89
89
  Override the default parameters of the :class:`PSGrid` instance.
@@ -389,32 +389,32 @@ def ps_nominal_grid(
389
389
  >>> perf.to_dataframe()
390
390
  aircraft_mass engine_efficiency fuel_flow
391
391
  level
392
- 200.0 58416.230844 0.308675 0.561244
393
- 210.0 61617.676623 0.308675 0.589307
394
- 220.0 64829.702583 0.308675 0.617369
395
- 230.0 68026.415693 0.308675 0.646423
396
- 240.0 71187.897058 0.308675 0.677265
397
- 250.0 71818.514829 0.308542 0.686129
398
- 260.0 71809.073819 0.308073 0.690896
399
- 270.0 71796.095265 0.307367 0.696978
400
- 280.0 71780.225852 0.306500 0.704144
401
- 290.0 71761.957365 0.305529 0.712217
392
+ 200.0 58416.230843 0.300958 0.575635
393
+ 210.0 61617.676624 0.300958 0.604417
394
+ 220.0 64829.702583 0.300958 0.633199
395
+ 230.0 68026.415695 0.300958 0.662998
396
+ 240.0 71187.897060 0.300958 0.694631
397
+ 250.0 71775.399825 0.300824 0.703349
398
+ 260.0 71765.716737 0.300363 0.708259
399
+ 270.0 71752.405400 0.299671 0.714514
400
+ 280.0 71736.129079 0.298823 0.721878
401
+ 290.0 71717.392170 0.297875 0.730169
402
402
 
403
403
  >>> # Now compute it for a higher Mach number
404
404
  >>> perf = ps_nominal_grid("A320", level=level, mach_number=0.78)
405
405
  >>> perf.to_dataframe()
406
406
  aircraft_mass engine_efficiency fuel_flow
407
407
  level
408
- 200.0 57857.463750 0.314462 0.580472
409
- 210.0 60626.062062 0.314467 0.605797
410
- 220.0 63818.498305 0.314467 0.634645
411
- 230.0 66993.691515 0.314467 0.664512
412
- 240.0 70129.930503 0.314467 0.696217
413
- 250.0 71747.933832 0.314423 0.714997
414
- 260.0 71735.433936 0.314098 0.721147
415
- 270.0 71719.057287 0.313542 0.728711
416
- 280.0 71699.595538 0.312830 0.737412
417
- 290.0 71677.628782 0.312016 0.747045
408
+ 200.0 57941.825236 0.306598 0.596100
409
+ 210.0 60626.062062 0.306605 0.621331
410
+ 220.0 63818.498306 0.306605 0.650918
411
+ 230.0 66993.691517 0.306605 0.681551
412
+ 240.0 70129.930503 0.306605 0.714069
413
+ 250.0 71703.009059 0.306560 0.732944
414
+ 260.0 71690.188652 0.306239 0.739276
415
+ 270.0 71673.392089 0.305694 0.747052
416
+ 280.0 71653.431321 0.304997 0.755990
417
+ 290.0 71630.901315 0.304201 0.765883
418
418
  """
419
419
  coords: dict[str, Any] | xrcc.DataArrayCoordinates
420
420
  if isinstance(air_temperature, xr.DataArray):
@@ -3,11 +3,14 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import dataclasses
6
+ import functools
7
+ import pathlib
6
8
  from collections.abc import Mapping
7
9
  from typing import Any, NoReturn, overload
8
10
 
9
11
  import numpy as np
10
12
  import numpy.typing as npt
13
+ import pandas as pd
11
14
  from overrides import overrides
12
15
 
13
16
  from pycontrails.core import flight
@@ -30,6 +33,9 @@ from pycontrails.utils.types import ArrayOrFloat
30
33
 
31
34
  # mypy: disable-error-code = "type-var, arg-type"
32
35
 
36
+ #: Path to the Poll-Schumann aircraft parameters CSV file.
37
+ PS_SYNONYM_FILE_PATH = pathlib.Path(__file__).parent / "static" / "ps-synonym-list-20240524.csv"
38
+
33
39
 
34
40
  @dataclasses.dataclass
35
41
  class PSFlightParams(AircraftPerformanceParams):
@@ -39,6 +45,13 @@ class PSFlightParams(AircraftPerformanceParams):
39
45
  #: efficiency to always exceed this value.
40
46
  eta_over_eta_b_min: float | None = 0.5
41
47
 
48
+ #: Account for "in-service" engine deterioration between maintenance cycles.
49
+ #: Default value is set to +2.5% increase in fuel consumption.
50
+ # Reference:
51
+ # Gurrola Arrieta, M.D.J., Botez, R.M. and Lasne, A., 2024. An Engine Deterioration Model for
52
+ # Predicting Fuel Consumption Impact in a Regional Aircraft. Aerospace, 11(6), p.426.
53
+ engine_deterioration_factor: float = 0.025
54
+
42
55
 
43
56
  class PSFlight(AircraftPerformance):
44
57
  """Simulate aircraft performance using Poll-Schumann (PS) model.
@@ -68,7 +81,10 @@ class PSFlight(AircraftPerformance):
68
81
  **params_kwargs: Any,
69
82
  ) -> None:
70
83
  super().__init__(met=met, params=params, **params_kwargs)
71
- self.aircraft_engine_params = load_aircraft_engine_params()
84
+ self.aircraft_engine_params = load_aircraft_engine_params(
85
+ self.params["engine_deterioration_factor"]
86
+ )
87
+ self.synonym_dict = get_aircraft_synonym_dict_ps()
72
88
 
73
89
  def check_aircraft_type_availability(
74
90
  self, aircraft_type: str, raise_error: bool = True
@@ -92,7 +108,7 @@ class PSFlight(AircraftPerformance):
92
108
  KeyError
93
109
  raises KeyError if the aircraft type is not covered by database
94
110
  """
95
- if aircraft_type in self.aircraft_engine_params:
111
+ if aircraft_type in self.aircraft_engine_params or aircraft_type in self.synonym_dict:
96
112
  return True
97
113
  if raise_error:
98
114
  msg = f"Aircraft type {aircraft_type} not covered by the PS model."
@@ -125,13 +141,15 @@ class PSFlight(AircraftPerformance):
125
141
  raise KeyError(msg) from exc
126
142
 
127
143
  try:
128
- aircraft_params = self.aircraft_engine_params[aircraft_type]
144
+ atyp_ps = self.synonym_dict.get(aircraft_type) or aircraft_type
145
+ aircraft_params = self.aircraft_engine_params[atyp_ps]
129
146
  except KeyError as exc:
130
147
  msg = f"Aircraft type {aircraft_type} not covered by the PS model."
131
148
  raise KeyError(msg) from exc
132
149
 
133
150
  # Set flight attributes based on engine, if they aren't already defined
134
151
  self.source.attrs.setdefault("aircraft_performance_model", self.name)
152
+ self.source.attrs.setdefault("aircraft_type_ps", atyp_ps)
135
153
  self.source.attrs.setdefault("n_engine", aircraft_params.n_engine)
136
154
 
137
155
  self.source.attrs.setdefault("wingspan", aircraft_params.wing_span)
@@ -148,7 +166,7 @@ class PSFlight(AircraftPerformance):
148
166
 
149
167
  # Run the simulation
150
168
  aircraft_performance = self.simulate_fuel_and_performance(
151
- aircraft_type=aircraft_type,
169
+ aircraft_type=atyp_ps,
152
170
  altitude_ft=self.source.altitude_ft,
153
171
  time=self.source["time"],
154
172
  true_airspeed=true_airspeed,
@@ -242,7 +260,7 @@ class PSFlight(AircraftPerformance):
242
260
 
243
261
  elif isinstance(time, np.ndarray):
244
262
  dt_sec = flight.segment_duration(time, dtype=altitude_ft.dtype)
245
- rocd = flight.segment_rocd(dt_sec, altitude_ft)
263
+ rocd = flight.segment_rocd(dt_sec, altitude_ft, air_temperature)
246
264
  dv_dt = jet.acceleration(true_airspeed, dt_sec)
247
265
  theta = jet.climb_descent_angle(true_airspeed, rocd)
248
266
 
@@ -299,7 +317,7 @@ class PSFlight(AircraftPerformance):
299
317
 
300
318
  # Flight phase
301
319
  segment_duration = flight.segment_duration(time, dtype=altitude_ft.dtype)
302
- rocd = flight.segment_rocd(segment_duration, altitude_ft)
320
+ rocd = flight.segment_rocd(segment_duration, altitude_ft, air_temperature)
303
321
 
304
322
  if correct_fuel_flow:
305
323
  flight_phase = flight.segment_phase(rocd, altitude_ft)
@@ -980,3 +998,20 @@ def fuel_flow_correction(
980
998
  descent = flight_phase == flight.FlightPhase.DESCENT
981
999
  ff_max[descent] = 0.3 * fuel_flow_max_sls
982
1000
  return np.clip(fuel_flow, ff_min, ff_max)
1001
+
1002
+
1003
+ @functools.cache
1004
+ def get_aircraft_synonym_dict_ps() -> dict[str, str]:
1005
+ """Read `ps-synonym-list-20240524.csv` from the static directory.
1006
+
1007
+ Returns
1008
+ -------
1009
+ dict[str, str]
1010
+ Dictionary of the form ``{"icao_aircraft_type": "ps_aircraft_type"}``.
1011
+ """
1012
+ # get path to static PS synonym list
1013
+ synonym_path = pathlib.Path(__file__).parent / "static" / "ps-synonym-list-20240524.csv"
1014
+ df_atyp_icao_to_ps = pd.read_csv(
1015
+ synonym_path, usecols=["ICAO Aircraft Code", "PS ATYP"], index_col=0
1016
+ )
1017
+ return df_atyp_icao_to_ps.squeeze("columns").to_dict()
@@ -118,7 +118,7 @@ def max_available_thrust_coefficient(
118
118
  c_t_eta_b: ArrayOrFloat,
119
119
  atyp_param: PSAircraftEngineParams,
120
120
  *,
121
- buffer: float = 0.10,
121
+ buffer: float = 0.20,
122
122
  ) -> ArrayOrFloat:
123
123
  """
124
124
  Calculate maximum available thrust coefficient.
@@ -134,8 +134,8 @@ def max_available_thrust_coefficient(
134
134
  atyp_param : PSAircraftEngineParams
135
135
  Extracted aircraft and engine parameters.
136
136
  buffer : float, optional
137
- Additional buffer for maximum available thrust coefficient. The default value is 0.05,
138
- which increases the maximum available thrust coefficient by 5%.
137
+ Additional buffer for maximum throttle parameter `tr_max`. The default value recommended by
138
+ Ian Poll is 0.2, which increases the maximum throttle parameter by 20%.
139
139
 
140
140
  Returns
141
141
  -------
@@ -148,9 +148,10 @@ def max_available_thrust_coefficient(
148
148
  atyp_param.tet_mcc,
149
149
  atyp_param.tr_ec,
150
150
  atyp_param.m_ec,
151
+ buffer=buffer,
151
152
  )
152
153
  c_t_max_over_c_t_eta_b = 1.0 + 2.5 * (tr_max - 1.0)
153
- return c_t_max_over_c_t_eta_b * c_t_eta_b * (1.0 + buffer)
154
+ return c_t_max_over_c_t_eta_b * c_t_eta_b
154
155
 
155
156
 
156
157
  def get_excess_thrust_available(
@@ -234,6 +235,8 @@ def _normalised_max_throttle_parameter(
234
235
  tet_mcc: float,
235
236
  tr_ec: float,
236
237
  m_ec: float,
238
+ *,
239
+ buffer: float = 0.20,
237
240
  ) -> ArrayOrFloat:
238
241
  """
239
242
  Calculate normalised maximum throttle parameter.
@@ -251,6 +254,10 @@ def _normalised_max_throttle_parameter(
251
254
  temperature for maximum overall efficiency.
252
255
  m_ec : float
253
256
  Engine characteristic Mach number associated with `tr_ec`.
257
+ buffer : float, optional
258
+ Additional buffer for maximum throttle parameter. The default value recommended by Ian Poll
259
+ is 0.2, which increases the maximum throttle parameter by 20%. This affects the maximum
260
+ available thrust coefficient calculated downstream.
254
261
 
255
262
  Returns
256
263
  -------
@@ -263,8 +270,10 @@ def _normalised_max_throttle_parameter(
263
270
  entry to the freestream total temperature, normalised with its value for maximum engine
264
271
  overall efficiency at the same freestream Mach number.
265
272
  """
266
- return (tet_mcc / air_temperature) / (
267
- tr_ec * (1.0 - 0.53 * (mach_number - m_ec) ** 2) * (1.0 + 0.2 * mach_number**2)
273
+ return (
274
+ (tet_mcc / air_temperature)
275
+ / (tr_ec * (1.0 - 0.53 * (mach_number - m_ec) ** 2) * (1.0 + 0.2 * mach_number**2))
276
+ * (1.0 + buffer)
268
277
  )
269
278
 
270
279