pycontrails 0.52.2__tar.gz → 0.53.0__tar.gz

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 (271) hide show
  1. {pycontrails-0.52.2 → pycontrails-0.53.0}/.github/workflows/doctest.yaml +2 -0
  2. {pycontrails-0.52.2 → pycontrails-0.53.0}/.github/workflows/release.yaml +3 -3
  3. {pycontrails-0.52.2 → pycontrails-0.53.0}/.github/workflows/test.yaml +2 -1
  4. {pycontrails-0.52.2 → pycontrails-0.53.0}/CHANGELOG.md +34 -0
  5. {pycontrails-0.52.2/pycontrails.egg-info → pycontrails-0.53.0}/PKG-INFO +6 -6
  6. {pycontrails-0.52.2 → pycontrails-0.53.0}/README.md +1 -1
  7. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/api.rst +11 -0
  8. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/install.rst +3 -1
  9. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/_version.py +2 -2
  10. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/cache.py +1 -1
  11. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/flight.py +8 -5
  12. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/flightplan.py +1 -1
  13. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/interpolation.py +3 -1
  14. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/met.py +190 -15
  15. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/met_var.py +1 -1
  16. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/models.py +5 -5
  17. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/rgi_cython.c +1209 -1193
  18. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/vector.py +5 -5
  19. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/_leo_utils/vis.py +10 -11
  20. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/_met_utils/metsource.py +13 -11
  21. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/ecmwf/era5.py +1 -1
  22. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/ecmwf/era5_model_level.py +1 -1
  23. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/ecmwf/hres_model_level.py +3 -3
  24. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/ecmwf/variables.py +3 -3
  25. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/gfs/gfs.py +4 -3
  26. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/landsat.py +10 -9
  27. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/ext/synthetic_flight.py +1 -1
  28. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/accf.py +1 -1
  29. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/apcemm/apcemm.py +5 -5
  30. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocip/cocip.py +98 -24
  31. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocip/cocip_params.py +21 -0
  32. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocip/output_formats.py +13 -4
  33. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocip/radiative_forcing.py +3 -3
  34. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocipgrid/cocip_grid.py +4 -4
  35. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/ps_model/ps_model.py +4 -4
  36. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/sac.py +2 -2
  37. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/physics/thermo.py +1 -1
  38. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/utils/json.py +16 -18
  39. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/utils/types.py +7 -6
  40. {pycontrails-0.52.2 → pycontrails-0.53.0/pycontrails.egg-info}/PKG-INFO +6 -6
  41. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails.egg-info/requires.txt +2 -2
  42. {pycontrails-0.52.2 → pycontrails-0.53.0}/pyproject.toml +8 -7
  43. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/_deprecated.py +2 -2
  44. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_cocip.py +140 -0
  45. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_fleet.py +2 -2
  46. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_flight.py +3 -1
  47. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_interpolation.py +122 -16
  48. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_met.py +2 -2
  49. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_models.py +3 -3
  50. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_ps_model.py +1 -1
  51. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_sac_issr.py +4 -1
  52. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_tau_cirrus.py +2 -2
  53. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_units.py +2 -2
  54. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_vector.py +1 -1
  55. {pycontrails-0.52.2 → pycontrails-0.53.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  56. {pycontrails-0.52.2 → pycontrails-0.53.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  57. {pycontrails-0.52.2 → pycontrails-0.53.0}/.github/dependabot.yaml +0 -0
  58. {pycontrails-0.52.2 → pycontrails-0.53.0}/.github/pull_request_template.md +0 -0
  59. {pycontrails-0.52.2 → pycontrails-0.53.0}/.github/workflows/docs.yaml +0 -0
  60. {pycontrails-0.52.2 → pycontrails-0.53.0}/.github/workflows/scorecard.yaml +0 -0
  61. {pycontrails-0.52.2 → pycontrails-0.53.0}/.gitignore +0 -0
  62. {pycontrails-0.52.2 → pycontrails-0.53.0}/.pre-commit-config.yaml +0 -0
  63. {pycontrails-0.52.2 → pycontrails-0.53.0}/.zenodo.json +0 -0
  64. {pycontrails-0.52.2 → pycontrails-0.53.0}/CONTRIBUTING.md +0 -0
  65. {pycontrails-0.52.2 → pycontrails-0.53.0}/LICENSE +0 -0
  66. {pycontrails-0.52.2 → pycontrails-0.53.0}/Makefile +0 -0
  67. {pycontrails-0.52.2 → pycontrails-0.53.0}/NOTICE +0 -0
  68. {pycontrails-0.52.2 → pycontrails-0.53.0}/RELEASE.md +0 -0
  69. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/_static/css/style.css +0 -0
  70. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/_static/img/colab.png +0 -0
  71. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/_static/img/favicon.png +0 -0
  72. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/_static/img/logo-dark.png +0 -0
  73. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/_static/img/logo.png +0 -0
  74. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/_static/pycontrails.bib +0 -0
  75. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/changelog.rst +0 -0
  76. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/conf.py +0 -0
  77. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/contributing.rst +0 -0
  78. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/develop.rst +0 -0
  79. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/flight.rst +0 -0
  80. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/index.rst +0 -0
  81. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/integrations/ACCF.ipynb +0 -0
  82. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/integrations/APCEMM.ipynb +0 -0
  83. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/literature.rst +0 -0
  84. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/meteorology.rst +0 -0
  85. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/models.rst +0 -0
  86. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/ARCO-ERA5.ipynb +0 -0
  87. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/AircraftPerformance.ipynb +0 -0
  88. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/Cache.ipynb +0 -0
  89. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/CoCiP.ipynb +0 -0
  90. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/ECMWF.ipynb +0 -0
  91. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/Flight.ipynb +0 -0
  92. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/GFS.ipynb +0 -0
  93. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/GOES.ipynb +0 -0
  94. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/ISSR.ipynb +0 -0
  95. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/Landsat.ipynb +0 -0
  96. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/Meteorology.ipynb +0 -0
  97. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/SAC.ipynb +0 -0
  98. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/Sentinel.ipynb +0 -0
  99. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/advection.ipynb +0 -0
  100. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/airports.ipynb +0 -0
  101. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/data/.gitignore +0 -0
  102. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/data/flight-ap.csv +0 -0
  103. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/data/flight-cocip.csv +0 -0
  104. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/data/flight-fdr.csv +0 -0
  105. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/data/flight-noisy.csv +0 -0
  106. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/data/flight.csv +0 -0
  107. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/data/iagos-flight-landsat.csv +0 -0
  108. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/data/iagos-flight-sentinel.csv +0 -0
  109. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/flightplan.ipynb +0 -0
  110. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/model-levels.ipynb +0 -0
  111. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/run-cocip-on-flight.ipynb +0 -0
  112. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/run-cocip-with-fdr.ipynb +0 -0
  113. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks/specific-humidity-interpolation.ipynb +0 -0
  114. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/notebooks.rst +0 -0
  115. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/observations.rst +0 -0
  116. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/tutorials.rst +0 -0
  117. {pycontrails-0.52.2 → pycontrails-0.53.0}/docs/utilities.rst +0 -0
  118. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/__init__.py +0 -0
  119. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/__init__.py +0 -0
  120. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/aircraft_performance.py +0 -0
  121. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/airports.py +0 -0
  122. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/coordinates.py +0 -0
  123. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/fleet.py +0 -0
  124. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/fuel.py +0 -0
  125. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/polygon.py +0 -0
  126. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/core/rgi_cython.pyx +0 -0
  127. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/__init__.py +0 -0
  128. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/_leo_utils/search.py +0 -0
  129. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/_leo_utils/static/bq_roi_query.sql +0 -0
  130. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/ecmwf/__init__.py +0 -0
  131. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/ecmwf/arco_era5.py +0 -0
  132. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/ecmwf/common.py +0 -0
  133. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/ecmwf/hres.py +0 -0
  134. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/ecmwf/ifs.py +0 -0
  135. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/ecmwf/model_levels.py +0 -0
  136. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv +0 -0
  137. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/gfs/__init__.py +0 -0
  138. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/gfs/variables.py +0 -0
  139. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/goes.py +0 -0
  140. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/sentinel.py +0 -0
  141. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/datalib/spire.py +0 -0
  142. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/ext/bada.py +0 -0
  143. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/ext/cirium.py +0 -0
  144. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/ext/empirical_grid.py +0 -0
  145. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/__init__.py +0 -0
  146. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/apcemm/__init__.py +0 -0
  147. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/apcemm/inputs.py +0 -0
  148. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/apcemm/static/apcemm_yaml_template.yaml +0 -0
  149. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/apcemm/utils.py +0 -0
  150. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocip/__init__.py +0 -0
  151. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocip/cocip_uncertainty.py +0 -0
  152. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocip/contrail_properties.py +0 -0
  153. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocip/radiative_heating.py +0 -0
  154. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocip/unterstrasser_wake_vortex.py +0 -0
  155. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocip/wake_vortex.py +0 -0
  156. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocip/wind_shear.py +0 -0
  157. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocipgrid/__init__.py +0 -0
  158. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/cocipgrid/cocip_grid_params.py +0 -0
  159. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/dry_advection.py +0 -0
  160. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/emissions/__init__.py +0 -0
  161. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/emissions/black_carbon.py +0 -0
  162. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/emissions/emissions.py +0 -0
  163. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/emissions/ffm2.py +0 -0
  164. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/emissions/static/default-engine-uids.csv +0 -0
  165. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/emissions/static/edb-gaseous-v29b-engines.csv +0 -0
  166. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/emissions/static/edb-nvpm-v29b-engines.csv +0 -0
  167. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/humidity_scaling/__init__.py +0 -0
  168. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/humidity_scaling/humidity_scaling.py +0 -0
  169. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq +0 -0
  170. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/humidity_scaling/quantiles/era5-pressure-level-quantiles.pq +0 -0
  171. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/issr.py +0 -0
  172. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/pcc.py +0 -0
  173. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/pcr.py +0 -0
  174. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/ps_model/__init__.py +0 -0
  175. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/ps_model/ps_aircraft_params.py +0 -0
  176. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/ps_model/ps_grid.py +0 -0
  177. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/ps_model/ps_operational_limits.py +0 -0
  178. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/ps_model/static/ps-aircraft-params-20240524.csv +0 -0
  179. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/ps_model/static/ps-synonym-list-20240524.csv +0 -0
  180. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/models/tau_cirrus.py +0 -0
  181. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/physics/__init__.py +0 -0
  182. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/physics/constants.py +0 -0
  183. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/physics/geo.py +0 -0
  184. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/physics/jet.py +0 -0
  185. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/physics/units.py +0 -0
  186. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/py.typed +0 -0
  187. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/utils/__init__.py +0 -0
  188. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/utils/dependencies.py +0 -0
  189. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/utils/iteration.py +0 -0
  190. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails/utils/temp.py +0 -0
  191. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails.egg-info/SOURCES.txt +0 -0
  192. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails.egg-info/dependency_links.txt +0 -0
  193. {pycontrails-0.52.2 → pycontrails-0.53.0}/pycontrails.egg-info/top_level.txt +0 -0
  194. {pycontrails-0.52.2 → pycontrails-0.53.0}/setup.cfg +0 -0
  195. {pycontrails-0.52.2 → pycontrails-0.53.0}/setup.py +0 -0
  196. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/__init__.py +0 -0
  197. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/benchmark/cocip/Makefile +0 -0
  198. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/benchmark/cocip/README.md +0 -0
  199. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/benchmark/cocip/benchmark.py +0 -0
  200. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/benchmark/cocip/compare.py +0 -0
  201. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/benchmark/cocip/data.md +0 -0
  202. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/benchmark/cocip/output.py +0 -0
  203. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/benchmark/cocip/review.ipynb +0 -0
  204. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/benchmark/cocip-fortran/README.md +0 -0
  205. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/benchmark/north-atlantic-study/.gcloudignore +0 -0
  206. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/benchmark/north-atlantic-study/README.md +0 -0
  207. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/benchmark/north-atlantic-study/support.py +0 -0
  208. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/benchmark/north-atlantic-study/validate.py +0 -0
  209. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/fixtures/cocip-met.py +0 -0
  210. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/fixtures/cocip-met2.py +0 -0
  211. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/fixtures/ecmwf-met.py +0 -0
  212. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/fixtures/gfs-met.py +0 -0
  213. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/__init__.py +0 -0
  214. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/conftest.py +0 -0
  215. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/NOAA_Solar_Calculations_day.csv +0 -0
  216. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/cocip-contrail-output.json +0 -0
  217. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/cocip-contrail-output2.json +0 -0
  218. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/cocip-flight-output.json +0 -0
  219. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/cocip-flight-output2.json +0 -0
  220. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/cocip-output-contrail-edges.json +0 -0
  221. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/cocip-output-flts-20190101-eu.pq +0 -0
  222. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/flight-cocip2.csv +0 -0
  223. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/flight-meridian.csv +0 -0
  224. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/flight-metadata.json +0 -0
  225. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/flight-spire-data-cleaning.pq +0 -0
  226. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/flight.csv +0 -0
  227. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/flt-wypts-20190101-eu.pq +0 -0
  228. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/met-20190101-eu.nc +0 -0
  229. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/met-accf-pl.nc +0 -0
  230. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/met-accf-sl.nc +0 -0
  231. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/met-ecmwf-pl.nc +0 -0
  232. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/met-ecmwf-sl.nc +0 -0
  233. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/met-era5-cocip1.nc +0 -0
  234. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/met-era5-cocip2.nc +0 -0
  235. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/met-gfs.nc +0 -0
  236. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/polygon-bug.nc +0 -0
  237. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/rad-20190101-eu.nc +0 -0
  238. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/rad-era5-cocip1.nc +0 -0
  239. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/rad-era5-cocip2.nc +0 -0
  240. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/static/rad-gfs.nc +0 -0
  241. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_accf.py +0 -0
  242. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_airports.py +0 -0
  243. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_apcemm.py +0 -0
  244. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_cache.py +0 -0
  245. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_cocip_grid.py +0 -0
  246. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_cocip_grid_parity.py +0 -0
  247. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_cocip_radiative_forcing.py +0 -0
  248. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_cocip_uncertainty.py +0 -0
  249. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_coordinates.py +0 -0
  250. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_datalib_metsource.py +0 -0
  251. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_dry_advection.py +0 -0
  252. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_dtypes.py +0 -0
  253. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_ecmwf.py +0 -0
  254. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_emissions.py +0 -0
  255. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_flightplan.py +0 -0
  256. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_fuel.py +0 -0
  257. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_geo.py +0 -0
  258. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_gfs.py +0 -0
  259. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_goes.py +0 -0
  260. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_grid_to_netcdf.py +0 -0
  261. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_humidity_scaling.py +0 -0
  262. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_init.py +0 -0
  263. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_leo.py +0 -0
  264. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_met_cache.py +0 -0
  265. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_pcc.py +0 -0
  266. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_polygons.py +0 -0
  267. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_spire.py +0 -0
  268. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_thermo_sac.py +0 -0
  269. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_unterstrasser_wake_vortex.py +0 -0
  270. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_utils.py +0 -0
  271. {pycontrails-0.52.2 → pycontrails-0.53.0}/tests/unit/test_zarr.py +0 -0
@@ -80,6 +80,8 @@ jobs:
80
80
  run: |
81
81
  make nb-test
82
82
 
83
+ # The doctests require numpy 2.0 or higher
83
84
  - name: Test docstrings
84
85
  run: |
86
+ pip install "numpy>=2.0"
85
87
  make doctest
@@ -40,13 +40,13 @@ jobs:
40
40
 
41
41
  # https://cibuildwheel.readthedocs.io/en/stable/options/#testing
42
42
  - name: Build wheels
43
- uses: pypa/cibuildwheel@v2.19
43
+ uses: pypa/cibuildwheel@v2.20
44
44
  env:
45
- CIBW_BUILD: cp39-* cp310-* cp311-* cp312-*
45
+ CIBW_BUILD: cp310-* cp311-* cp312-* cp313-*
46
46
  CIBW_SKIP: '*-win32 *-manylinux_i686 *-musllinux*'
47
47
  CIBW_BUILD_VERBOSITY: 3
48
48
  CIBW_ARCHS_MACOS: x86_64 arm64
49
- CIBW_TEST_SKIP: '*-macosx_arm64'
49
+ CIBW_TEST_SKIP: '*-macosx_arm64 cp313-*'
50
50
  # Completely isolate tests to prevent cibuildwheel from importing the
51
51
  # source instead of the wheel. This happens when tests/__init__.py is read.
52
52
  CIBW_TEST_EXTRAS: "complete,dev"
@@ -38,7 +38,8 @@ jobs:
38
38
  fail-fast: false
39
39
  matrix:
40
40
  os: [ubuntu-latest, windows-latest]
41
- pyversion: ['3.9', '3.10', '3.11', '3.12']
41
+ # TODO(Fall 2024): Add 3.13 once all dependencies are available
42
+ pyversion: ['3.10', '3.11', '3.12']
42
43
  runs-on: ${{ matrix.os }}
43
44
 
44
45
  steps:
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.53.0
4
+
5
+ ### Breaking changes
6
+
7
+ - Drop python 3.9 support per [NEP 29](https://numpy.org/neps/nep-0029-deprecation_policy.html).
8
+
9
+ ### Features
10
+
11
+ - Build wheels for [python 3.13](https://peps.python.org/pep-0719/). (These wheels are available on PyPI, but other pycontrails dependencies may not yet support python 3.13.)
12
+
13
+ ### Fixes
14
+
15
+ - Fix `PycontrailsRegularGridInterpolator` for compatibility with the latest scipy version.
16
+
17
+ ### Internals
18
+
19
+ - Defer import of `skimage` and `rasterio`.
20
+
21
+ ## v0.52.3
22
+
23
+ ### Features
24
+
25
+ - Add experimental `preprocess_lowmem` parameter to `Cocip`. When set to `True`, `Cocip` will attempt to reduce memory consumption during flight preprocessing and initial formation/persistence calculations by using an alternate implementation of `MetDataArray.interpolate` (see below).
26
+ - Add `lowmem` keyword-only argument to `MetDataArray.interpolate`. When `True`, attempt to reduce memory consumption by using an alternative interpolation strategy that loads at most two time steps of meteorology data into memory at a time.
27
+
28
+ ### Fixes
29
+
30
+ - Defer import of `matplotlib` in `models.cocip.output_formats`.
31
+ - Fix bug in `PycontrailsRegularGridInterpolator` that caused errors when dispatching to 1-d linear interpolation from in `rgi_cython.pyx`.
32
+
33
+ ### Internals
34
+
35
+ - Implement low-memory paths in `Cocip.eval` and `MetDataArray.interpolate`.
36
+
3
37
  ## v0.52.2
4
38
 
5
39
  ### Breaking changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pycontrails
3
- Version: 0.52.2
3
+ Version: 0.53.0
4
4
  Summary: Python library for modeling aviation climate impacts
5
5
  Author-email: Breakthrough Energy <py@contrails.org>
6
6
  License: Apache-2.0
@@ -14,16 +14,16 @@ Classifier: Intended Audience :: Science/Research
14
14
  Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.9
18
17
  Classifier: Programming Language :: Python :: 3.10
19
18
  Classifier: Programming Language :: Python :: 3.11
20
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
21
  Classifier: Programming Language :: Python :: 3 :: Only
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
24
24
  Classifier: Topic :: Scientific/Engineering :: GIS
25
25
  Classifier: Typing :: Typed
26
- Requires-Python: >=3.9
26
+ Requires-Python: >=3.10
27
27
  Description-Content-Type: text/markdown
28
28
  License-File: LICENSE
29
29
  License-File: NOTICE
@@ -36,7 +36,7 @@ Requires-Dist: xarray>=2022.3
36
36
  Provides-Extra: complete
37
37
  Requires-Dist: pycontrails[ecmwf,gcp,gfs,jupyter,pyproj,sat,vis,zarr]; extra == "complete"
38
38
  Provides-Extra: dev
39
- Requires-Dist: black[jupyter]==24.4.2; extra == "dev"
39
+ Requires-Dist: black[jupyter]==24.8.0; extra == "dev"
40
40
  Requires-Dist: dep_license; extra == "dev"
41
41
  Requires-Dist: fastparquet>=0.8; extra == "dev"
42
42
  Requires-Dist: ipdb>=0.13; extra == "dev"
@@ -50,7 +50,7 @@ Requires-Dist: pyarrow>=5.0; extra == "dev"
50
50
  Requires-Dist: pytest>=8.2; extra == "dev"
51
51
  Requires-Dist: pytest-cov>=2.11; extra == "dev"
52
52
  Requires-Dist: requests>=2.25; extra == "dev"
53
- Requires-Dist: ruff==0.5.3; extra == "dev"
53
+ Requires-Dist: ruff==0.5.7; extra == "dev"
54
54
  Requires-Dist: setuptools; extra == "dev"
55
55
  Provides-Extra: docs
56
56
  Requires-Dist: doc8>=1.1; extra == "docs"
@@ -141,7 +141,7 @@ Documentation and examples available at [py.contrails.org](https://py.contrails.
141
141
 
142
142
  ### Install with pip
143
143
 
144
- You can install pycontrails from PyPI with `pip` (Python 3.9 or later required):
144
+ You can install pycontrails from PyPI with `pip` (Python 3.10 or later required):
145
145
 
146
146
  ```bash
147
147
  $ pip install pycontrails
@@ -25,7 +25,7 @@ Documentation and examples available at [py.contrails.org](https://py.contrails.
25
25
 
26
26
  ### Install with pip
27
27
 
28
- You can install pycontrails from PyPI with `pip` (Python 3.9 or later required):
28
+ You can install pycontrails from PyPI with `pip` (Python 3.10 or later required):
29
29
 
30
30
  ```bash
31
31
  $ pip install pycontrails
@@ -134,6 +134,7 @@ CoCiP
134
134
 
135
135
  models.cocip.Cocip
136
136
  models.cocip.CocipParams
137
+ models.cocip.CocipFlightParams
137
138
  models.cocip.contrail_properties
138
139
  models.cocip.radiative_forcing
139
140
  models.cocip.wake_vortex
@@ -150,6 +151,16 @@ Gridded CoCiP
150
151
  models.cocipgrid.CocipGridParams
151
152
 
152
153
 
154
+ Dry Advection
155
+ """""""""""""
156
+
157
+ .. autosummary::
158
+ :toctree: api/
159
+
160
+ models.dry_advection.DryAdvection
161
+ models.dry_advection.DryAdvectionParams
162
+
163
+
153
164
  ACCF
154
165
  """"
155
166
 
@@ -17,7 +17,7 @@ The conda-forge package includes all optional runtime dependencies.
17
17
  pip install
18
18
  -----------
19
19
 
20
- With Python 3.9 or later, install the latest release from PyPI using ``pip``:
20
+ With Python 3.10 or later, install the latest release from PyPI using ``pip``:
21
21
 
22
22
  .. code-block:: bash
23
23
 
@@ -27,6 +27,8 @@ With Python 3.9 or later, install the latest release from PyPI using ``pip``:
27
27
  # install with all optional dependencies
28
28
  $ pip install "pycontrails[complete]"
29
29
 
30
+ Wheels are currently built for python 3.10 - 3.13 on Linux, macOS, and Windows. The python 3.13
31
+ wheels are not yet tested in CI/CD and not all runtime dependencies are available for python 3.13.
30
32
 
31
33
  Install the latest development version directly from GitHub:
32
34
 
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.52.2'
16
- __version_tuple__ = version_tuple = (0, 52, 2)
15
+ __version__ = version = '0.53.0'
16
+ __version_tuple__ = version_tuple = (0, 53, 0)
@@ -146,7 +146,7 @@ class CacheStore(ABC):
146
146
  """
147
147
 
148
148
  # TODO: run in parallel?
149
- return [self.put(d, cp) for d, cp in zip(data_path, cache_path)]
149
+ return [self.put(d, cp) for d, cp in zip(data_path, cache_path, strict=True)]
150
150
 
151
151
  # In the three methods below, child classes have a complete docstring.
152
152
 
@@ -1356,7 +1356,7 @@ class Flight(GeoVectorDataset):
1356
1356
  # NOTE: geod.npts does not return the initial or terminal points
1357
1357
  lonlats: list[tuple[float, float]] = geod.npts(lon0, lat0, lon1, lat1, n_steps)
1358
1358
 
1359
- lons, lats = zip(*lonlats)
1359
+ lons, lats = zip(*lonlats, strict=True)
1360
1360
  longitudes.extend(lons)
1361
1361
  latitudes.extend(lats)
1362
1362
 
@@ -1657,10 +1657,11 @@ def _return_linestring(data: dict[str, npt.NDArray[np.float64]]) -> list[list[fl
1657
1657
  The list of coordinates
1658
1658
  """
1659
1659
  # rounding to reduce the size of resultant json arrays
1660
- points = zip( # pylint: disable=zip-builtin-not-iterating
1660
+ points = zip(
1661
1661
  np.round(data["longitude"], decimals=4),
1662
1662
  np.round(data["latitude"], decimals=4),
1663
1663
  np.round(data["altitude"], decimals=4),
1664
+ strict=True,
1664
1665
  )
1665
1666
  return [list(p) for p in points]
1666
1667
 
@@ -1949,7 +1950,9 @@ def _altitude_interpolation_climb_descend_middle(
1949
1950
  # Form array of cumulative altitude values if the flight were to climb
1950
1951
  # at nominal_rocd over each group of nan
1951
1952
  cumalt_list = []
1952
- for start_na_idx, end_na_idx, size in zip(start_na_idxs, end_na_idxs, na_group_size):
1953
+ for start_na_idx, end_na_idx, size in zip(
1954
+ start_na_idxs, end_na_idxs, na_group_size, strict=True
1955
+ ):
1953
1956
  if s[start_na_idx] <= s[end_na_idx]:
1954
1957
  cumalt_list.append(np.arange(1, size, dtype=float))
1955
1958
  else:
@@ -2053,7 +2056,7 @@ def filter_altitude(
2053
2056
  --------
2054
2057
  :meth:`traffic.core.flight.Flight.filter`
2055
2058
  :func:`scipy.signal.medfilt`
2056
- """ # noqa: E501
2059
+ """
2057
2060
  if not len(altitude_ft):
2058
2061
  raise ValueError("Altitude must have non-zero length to filter")
2059
2062
 
@@ -2114,7 +2117,7 @@ def filter_altitude(
2114
2117
 
2115
2118
  result = np.copy(altitude_ft)
2116
2119
  if np.any(start_idxs):
2117
- for i0, i1 in zip(start_idxs, end_idxs):
2120
+ for i0, i1 in zip(start_idxs, end_idxs, strict=True):
2118
2121
  result[i0:i1] = altitude_filt[i0:i1]
2119
2122
 
2120
2123
  # reapply Savitzky-Golay filter to smooth climb and descent
@@ -93,7 +93,7 @@ def parse_atc_plan(atc_plan: str) -> dict[str, str]:
93
93
  See Also
94
94
  --------
95
95
  :func:`to_atc_plan`
96
- """ # noqa: E501
96
+ """
97
97
  atc_plan = atc_plan.replace("\r", "")
98
98
  atc_plan = atc_plan.replace("\n", "")
99
99
  atc_plan = atc_plan.upper()
@@ -76,6 +76,7 @@ class PycontrailsRegularGridInterpolator(scipy.interpolate.RegularGridInterpolat
76
76
  self.method = _pick_method(scipy.__version__, method)
77
77
  self.bounds_error = bounds_error
78
78
  self.fill_value = fill_value
79
+ self._spline = None
79
80
 
80
81
  def _prepare_xi_simple(self, xi: npt.NDArray[np.float64]) -> npt.NDArray[np.bool_]:
81
82
  """Run looser version of :meth:`_prepare_xi`.
@@ -215,7 +216,8 @@ class PycontrailsRegularGridInterpolator(scipy.interpolate.RegularGridInterpolat
215
216
 
216
217
  if ndim == 1:
217
218
  # np.interp could be better ... although that may also promote the dtype
218
- return rgi_cython.evaluate_linear_1d(values, indices, norm_distances, out)
219
+ # 1-d view is required for evaluate_linear_1d
220
+ return rgi_cython.evaluate_linear_1d(values, indices[0, :], norm_distances[0, :], out)
219
221
 
220
222
  msg = f"Invalid number of dimensions: {ndim}"
221
223
  raise ValueError(msg)
@@ -9,7 +9,15 @@ import pathlib
9
9
  import typing
10
10
  import warnings
11
11
  from abc import ABC, abstractmethod
12
- from collections.abc import Hashable, Iterable, Iterator, Mapping, MutableMapping, Sequence
12
+ from collections.abc import (
13
+ Generator,
14
+ Hashable,
15
+ Iterable,
16
+ Iterator,
17
+ Mapping,
18
+ MutableMapping,
19
+ Sequence,
20
+ )
13
21
  from contextlib import ExitStack
14
22
  from datetime import datetime
15
23
  from typing import (
@@ -62,12 +70,12 @@ class MetBase(ABC, Generic[XArrayType]):
62
70
  cachestore: CacheStore | None
63
71
 
64
72
  #: Default dimension order for DataArray or Dataset (x, y, z, t)
65
- dim_order: list[Hashable] = [
73
+ dim_order: tuple[Hashable, Hashable, Hashable, Hashable] = (
66
74
  "longitude",
67
75
  "latitude",
68
76
  "level",
69
77
  "time",
70
- ]
78
+ )
71
79
 
72
80
  def __repr__(self) -> str:
73
81
  data = getattr(self, "data", None)
@@ -192,10 +200,8 @@ class MetBase(ABC, Generic[XArrayType]):
192
200
  def _validate_transpose(self) -> None:
193
201
  """Check that data is transposed according to :attr:`dim_order`."""
194
202
 
195
- dims_tuple = tuple(self.dim_order)
196
-
197
203
  def _check_da(da: xr.DataArray, key: Hashable | None = None) -> None:
198
- if da.dims != dims_tuple:
204
+ if da.dims != self.dim_order:
199
205
  if key is not None:
200
206
  msg = (
201
207
  f"Data dimension not transposed on variable '{key}'. Initiate with"
@@ -263,7 +269,7 @@ class MetBase(ABC, Generic[XArrayType]):
263
269
  self.data["time"] = self.data["time"].astype("datetime64[ns]", copy=False)
264
270
 
265
271
  # sortby to ensure each coordinate has ascending order
266
- self.data = self.data.sortby(self.dim_order, ascending=True)
272
+ self.data = self.data.sortby(list(self.dim_order), ascending=True)
267
273
 
268
274
  if not self.is_wrapped:
269
275
  # Ensure longitude is contained in interval [-180, 180)
@@ -285,7 +291,7 @@ class MetBase(ABC, Generic[XArrayType]):
285
291
  self._validate_latitude()
286
292
 
287
293
  # transpose to have ordering (x, y, z, t, ...)
288
- dim_order = self.dim_order + [d for d in self.data.dims if d not in self.dim_order]
294
+ dim_order = [*self.dim_order, *(d for d in self.data.dims if d not in self.dim_order)]
289
295
  self.data = self.data.transpose(*dim_order)
290
296
 
291
297
  # single level data
@@ -481,7 +487,7 @@ class MetBase(ABC, Generic[XArrayType]):
481
487
  self.cachestore = self.cachestore or DiskCacheStore()
482
488
 
483
489
  # group by hour and save one dataset for each hour to temp file
484
- times, datasets = zip(*dataset.groupby("time", squeeze=False))
490
+ times, datasets = zip(*dataset.groupby("time", squeeze=False), strict=True)
485
491
 
486
492
  # Open ExitStack to control temp_file context manager
487
493
  with ExitStack() as stack:
@@ -912,7 +918,7 @@ class MetDataset(MetBase):
912
918
  KeyError
913
919
  Raises when dataset does not contain variable in ``vars``
914
920
  """
915
- if isinstance(vars, (MetVariable, str)):
921
+ if isinstance(vars, MetVariable | str):
916
922
  vars = (vars,)
917
923
 
918
924
  met_keys: list[str] = []
@@ -1014,7 +1020,7 @@ class MetDataset(MetBase):
1014
1020
 
1015
1021
  @overrides
1016
1022
  def broadcast_coords(self, name: str) -> xr.DataArray:
1017
- da = xr.ones_like(self.data[list(self.data.keys())[0]]) * self.data[name]
1023
+ da = xr.ones_like(self.data[next(iter(self.data.keys()))]) * self.data[name]
1018
1024
  da.name = name
1019
1025
 
1020
1026
  return da
@@ -1066,7 +1072,7 @@ class MetDataset(MetBase):
1066
1072
  coords_vals = [indexes[key].values for key in coords_keys]
1067
1073
  coords_meshes = np.meshgrid(*coords_vals, indexing="ij")
1068
1074
  raveled_coords = (mesh.ravel() for mesh in coords_meshes)
1069
- data = dict(zip(coords_keys, raveled_coords))
1075
+ data = dict(zip(coords_keys, raveled_coords, strict=True))
1070
1076
 
1071
1077
  out = vector_module.GeoVectorDataset(data, copy=False)
1072
1078
  for key, da in self.data.items():
@@ -1502,6 +1508,7 @@ class MetDataArray(MetBase):
1502
1508
  bounds_error: bool = ...,
1503
1509
  fill_value: float | np.float64 | None = ...,
1504
1510
  localize: bool = ...,
1511
+ lowmem: bool = ...,
1505
1512
  indices: interpolation.RGIArtifacts | None = ...,
1506
1513
  return_indices: Literal[False] = ...,
1507
1514
  ) -> npt.NDArray[np.float64]: ...
@@ -1518,6 +1525,7 @@ class MetDataArray(MetBase):
1518
1525
  bounds_error: bool = ...,
1519
1526
  fill_value: float | np.float64 | None = ...,
1520
1527
  localize: bool = ...,
1528
+ lowmem: bool = ...,
1521
1529
  indices: interpolation.RGIArtifacts | None = ...,
1522
1530
  return_indices: Literal[True],
1523
1531
  ) -> tuple[npt.NDArray[np.float64], interpolation.RGIArtifacts]: ...
@@ -1533,6 +1541,7 @@ class MetDataArray(MetBase):
1533
1541
  bounds_error: bool = False,
1534
1542
  fill_value: float | np.float64 | None = np.nan,
1535
1543
  localize: bool = False,
1544
+ lowmem: bool = False,
1536
1545
  indices: interpolation.RGIArtifacts | None = None,
1537
1546
  return_indices: bool = False,
1538
1547
  ) -> npt.NDArray[np.float64] | tuple[npt.NDArray[np.float64], interpolation.RGIArtifacts]:
@@ -1540,7 +1549,9 @@ class MetDataArray(MetBase):
1540
1549
 
1541
1550
  Zero dimensional coordinates are reshaped to 1D arrays.
1542
1551
 
1543
- Method automatically loads underlying :attr:`data` into memory.
1552
+ If ``lowmem == False``, method automatically loads underlying :attr:`data` into
1553
+ memory. Otherwise, method iterates through smaller subsets of :attr:`data` and releases
1554
+ subsets from memory once interpolation against each subset is finished.
1544
1555
 
1545
1556
  If ``method == "nearest"``, the out array will have the same ``dtype`` as
1546
1557
  the underlying :attr:`data`.
@@ -1586,10 +1597,18 @@ class MetDataArray(MetBase):
1586
1597
  localize: bool, optional
1587
1598
  Experimental. If True, downselect gridded data to smallest bounding box containing
1588
1599
  all points. By default False.
1600
+ lowmem: bool, optional
1601
+ Experimental. If True, iterate through points binned by the time coordinate of the
1602
+ grided data, and downselect gridded data to the smallest bounding box containing
1603
+ each binned set of point *before loading into memory*. This can significantly reduce
1604
+ memory consumption with large numbers of points at the cost of increased runtime.
1605
+ By default False.
1589
1606
  indices: tuple | None, optional
1590
1607
  Experimental. See :func:`interpolation.interp`. None by default.
1591
1608
  return_indices: bool, optional
1592
1609
  Experimental. See :func:`interpolation.interp`. False by default.
1610
+ Note that values returned differ when ``lowmem=True`` and ``lowmem=False``,
1611
+ so output should only be re-used in calls with the same ``lowmem`` value.
1593
1612
 
1594
1613
  Returns
1595
1614
  -------
@@ -1632,10 +1651,29 @@ class MetDataArray(MetBase):
1632
1651
  >>> level = np.linspace(200, 300, 10)
1633
1652
  >>> time = pd.date_range("2022-03-01T14", periods=10, freq="5min")
1634
1653
  >>> mda.interpolate(longitude, latitude, level, time)
1654
+ array([220.44347694, 223.08900738, 225.74338924, 228.41642088,
1655
+ 231.10858599, 233.54857391, 235.71504913, 237.86478872,
1656
+ 239.99274623, 242.10792167])
1657
+
1658
+ >>> # Can easily switch to alternative low-memory implementation
1659
+ >>> mda.interpolate(longitude, latitude, level, time, lowmem=True)
1635
1660
  array([220.44347694, 223.08900738, 225.74338924, 228.41642088,
1636
1661
  231.10858599, 233.54857391, 235.71504913, 237.86478872,
1637
1662
  239.99274623, 242.10792167])
1638
1663
  """
1664
+ if lowmem:
1665
+ return self._interp_lowmem(
1666
+ longitude,
1667
+ latitude,
1668
+ level,
1669
+ time,
1670
+ method=method,
1671
+ bounds_error=bounds_error,
1672
+ fill_value=fill_value,
1673
+ indices=indices,
1674
+ return_indices=return_indices,
1675
+ )
1676
+
1639
1677
  # Load if necessary
1640
1678
  if not self.in_memory:
1641
1679
  self._check_memory("Interpolation over")
@@ -1660,6 +1698,100 @@ class MetDataArray(MetBase):
1660
1698
  return_indices=return_indices,
1661
1699
  )
1662
1700
 
1701
+ def _interp_lowmem(
1702
+ self,
1703
+ longitude: float | npt.NDArray[np.float64],
1704
+ latitude: float | npt.NDArray[np.float64],
1705
+ level: float | npt.NDArray[np.float64],
1706
+ time: np.datetime64 | npt.NDArray[np.datetime64],
1707
+ *,
1708
+ method: str = "linear",
1709
+ bounds_error: bool = False,
1710
+ fill_value: float | np.float64 | None = np.nan,
1711
+ minimize_memory: bool = False,
1712
+ indices: interpolation.RGIArtifacts | None = None,
1713
+ return_indices: bool = False,
1714
+ ) -> npt.NDArray[np.float64] | tuple[npt.NDArray[np.float64], interpolation.RGIArtifacts]:
1715
+ """Interpolate values against underlying DataArray.
1716
+
1717
+ This method is used by :meth:`interpolate` when ``lowmem=True``.
1718
+ Parameters and return types are identical to :meth:`interpolate`, except
1719
+ that the ``localize`` keyword argument is omitted.
1720
+ """
1721
+ # Convert all inputs to 1d arrays
1722
+ # Not validating against ndim >= 2
1723
+ longitude, latitude, level, time = np.atleast_1d(longitude, latitude, level, time)
1724
+
1725
+ if bounds_error:
1726
+ _lowmem_boundscheck(time, self.data)
1727
+
1728
+ # Create buffers for holding interpolation output
1729
+ # Use np.full rather than np.empty so points not covered
1730
+ # by masks are filled with correct out-of-bounds values.
1731
+ out = np.full(longitude.shape, fill_value, dtype=self.data.dtype)
1732
+ if return_indices:
1733
+ rgi_artifacts = interpolation.RGIArtifacts(
1734
+ xi_indices=np.full((4, longitude.size), -1, dtype=np.int64),
1735
+ norm_distances=np.full((4, longitude.size), np.nan, dtype=np.float64),
1736
+ out_of_bounds=np.full((longitude.size,), True, dtype=np.bool_),
1737
+ )
1738
+
1739
+ # Iterate over portions of points between adjacent time steps in gridded data
1740
+ for mask in _lowmem_masks(time, self.data["time"].values):
1741
+ if mask is None or not np.any(mask):
1742
+ continue
1743
+
1744
+ lon_sl = longitude[mask]
1745
+ lat_sl = latitude[mask]
1746
+ lev_sl = level[mask]
1747
+ t_sl = time[mask]
1748
+ if indices is not None:
1749
+ indices_sl = interpolation.RGIArtifacts(
1750
+ xi_indices=indices.xi_indices[:, mask],
1751
+ norm_distances=indices.norm_distances[:, mask],
1752
+ out_of_bounds=indices.out_of_bounds[mask],
1753
+ )
1754
+ else:
1755
+ indices_sl = None
1756
+
1757
+ coords = {"longitude": lon_sl, "latitude": lat_sl, "level": lev_sl, "time": t_sl}
1758
+ if any(np.all(np.isnan(coord)) for coord in coords.values()):
1759
+ continue
1760
+ da = interpolation._localize(self.data, coords)
1761
+ if not da._in_memory:
1762
+ logger.debug(
1763
+ "Loading %s MB subset of %s into memory.",
1764
+ round(da.nbytes / 1_000_000, 2),
1765
+ da.name,
1766
+ )
1767
+ da.load()
1768
+
1769
+ tmp = interpolation.interp(
1770
+ longitude=lon_sl,
1771
+ latitude=lat_sl,
1772
+ level=lev_sl,
1773
+ time=t_sl,
1774
+ da=da,
1775
+ method=method,
1776
+ bounds_error=bounds_error,
1777
+ fill_value=fill_value,
1778
+ localize=False, # would be no-op; da is localized already
1779
+ indices=indices_sl,
1780
+ return_indices=return_indices,
1781
+ )
1782
+
1783
+ if return_indices:
1784
+ out[mask], rgi_sl = tmp
1785
+ rgi_artifacts.xi_indices[:, mask] = rgi_sl.xi_indices
1786
+ rgi_artifacts.norm_distances[:, mask] = rgi_sl.norm_distances
1787
+ rgi_artifacts.out_of_bounds[mask] = rgi_sl.out_of_bounds
1788
+ else:
1789
+ out[mask] = tmp
1790
+
1791
+ if return_indices:
1792
+ return out, rgi_artifacts
1793
+ return out
1794
+
1663
1795
  def _check_memory(self, msg_start: str) -> None:
1664
1796
  """Check the memory usage of the underlying data.
1665
1797
 
@@ -1731,7 +1863,7 @@ class MetDataArray(MetBase):
1731
1863
  cachestore = cachestore or DiskCacheStore()
1732
1864
  chunks = chunks or {}
1733
1865
  data = _load(hash, cachestore, chunks)
1734
- return cls(data[list(data.data_vars)[0]])
1866
+ return cls(data[next(iter(data.data_vars))])
1735
1867
 
1736
1868
  @property
1737
1869
  def proportion(self) -> float:
@@ -2124,7 +2256,7 @@ class MetDataArray(MetBase):
2124
2256
  -----
2125
2257
  Uses the `scikit-image Marching Cubes <https://scikit-image.org/docs/dev/auto_examples/edges/plot_marching_cubes.html>`_
2126
2258
  algorithm to reconstruct a surface from the point-cloud like arrays.
2127
- """ # noqa: E501
2259
+ """
2128
2260
  try:
2129
2261
  from skimage import measure
2130
2262
  except ModuleNotFoundError as e:
@@ -2656,3 +2788,46 @@ def _add_vertical_coords(data: XArrayType) -> XArrayType:
2656
2788
  data.coords["altitude"] = data.coords["altitude"].astype(dtype, copy=False)
2657
2789
 
2658
2790
  return data
2791
+
2792
+
2793
+ def _lowmem_boundscheck(time: npt.NDArray[np.datetime64], da: xr.DataArray) -> None:
2794
+ """Extra bounds check required with low-memory interpolation strategy.
2795
+
2796
+ Because the main loop in `_interp_lowmem` processes points between time steps
2797
+ in gridded data, it will never encounter points that are out-of-bounds in time
2798
+ and may fail to produce requested out-of-bounds errors.
2799
+ """
2800
+ da_time = da["time"].to_numpy()
2801
+ if not np.all((time >= da_time.min()) & (time <= da_time.max())):
2802
+ axis = da.get_axis_num("time")
2803
+ msg = f"One of the requested xi is out of bounds in dimension {axis}"
2804
+ raise ValueError(msg)
2805
+
2806
+
2807
+ def _lowmem_masks(
2808
+ time: npt.NDArray[np.datetime64], t_met: npt.NDArray[np.datetime64]
2809
+ ) -> Generator[npt.NDArray[np.bool_], None, None]:
2810
+ """Generate sequence of masks for low-memory interpolation."""
2811
+ t_met_max = t_met.max()
2812
+ t_met_min = t_met.min()
2813
+ inbounds = (time >= t_met_min) & (time <= t_met_max)
2814
+ if not np.any(inbounds):
2815
+ return
2816
+
2817
+ earliest = np.nanmin(time)
2818
+ istart = 0 if earliest < t_met_min else np.flatnonzero(t_met <= earliest).max()
2819
+ latest = np.nanmax(time)
2820
+ iend = t_met.size - 1 if latest > t_met_max else np.flatnonzero(t_met >= latest).min()
2821
+ if istart == iend:
2822
+ yield inbounds
2823
+ return
2824
+
2825
+ # Sequence of masks covers elements in time in the interval [t_met[istart], t_met[iend]].
2826
+ # The first iteration masks elements in the interval [t_met[istart], t_met[istart+1]]
2827
+ # (inclusive of both endpoints).
2828
+ # Subsequent iterations mask elements in the interval (t_met[i], t_met[i+1]]
2829
+ # (inclusive of right endpoint only).
2830
+ for i in range(istart, iend):
2831
+ mask = ((time >= t_met[i]) if i == istart else (time > t_met[i])) & (time <= t_met[i + 1])
2832
+ if np.any(mask):
2833
+ yield mask
@@ -22,7 +22,7 @@ class MetVariable:
22
22
  - `NCEP Grib v2 Code Table <https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-2.shtml>`_
23
23
 
24
24
  Used for defining support parameters in a grib-like fashion.
25
- """ # noqa: E501
25
+ """
26
26
 
27
27
  #: Short variable name.
28
28
  #: Chosen for greatest consistency between data sources.
@@ -11,7 +11,7 @@ import warnings
11
11
  from abc import ABC, abstractmethod
12
12
  from collections.abc import Sequence
13
13
  from dataclasses import dataclass, fields
14
- from typing import Any, NoReturn, TypeVar, Union, overload
14
+ from typing import Any, NoReturn, TypeVar, overload
15
15
 
16
16
  import numpy as np
17
17
  import numpy.typing as npt
@@ -30,13 +30,13 @@ from pycontrails.utils.types import type_guard
30
30
  logger = logging.getLogger(__name__)
31
31
 
32
32
  #: Model input source types
33
- ModelInput = Union[MetDataset, GeoVectorDataset, Flight, Sequence[Flight], None]
33
+ ModelInput = MetDataset | GeoVectorDataset | Flight | Sequence[Flight] | None
34
34
 
35
35
  #: Model output source types
36
- ModelOutput = Union[MetDataArray, MetDataset, GeoVectorDataset, Flight, list[Flight], NoReturn]
36
+ ModelOutput = MetDataArray | MetDataset | GeoVectorDataset | Flight | list[Flight]
37
37
 
38
38
  #: Model attribute source types
39
- SourceType = Union[MetDataset, GeoVectorDataset, Flight, Fleet]
39
+ SourceType = MetDataset | GeoVectorDataset | Flight | Fleet
40
40
 
41
41
  _Source = TypeVar("_Source")
42
42
 
@@ -453,7 +453,7 @@ class Model(ABC):
453
453
  return Fleet.from_seq(source)
454
454
 
455
455
  # Raise error if source is not a MetDataset or GeoVectorDataset
456
- if not isinstance(source, (MetDataset, GeoVectorDataset)):
456
+ if not isinstance(source, MetDataset | GeoVectorDataset):
457
457
  msg = f"Unknown source type: {type(source)}"
458
458
  raise TypeError(msg)
459
459