windkit 1.0.2__tar.gz → 2.0.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.
Files changed (116) hide show
  1. {windkit-1.0.2 → windkit-2.0.0}/CHANGELOG.md +41 -2
  2. windkit-2.0.0/PERFORMANCE_CI_PLAN.md +294 -0
  3. windkit-2.0.0/PKG-INFO +115 -0
  4. windkit-2.0.0/README.md +66 -0
  5. {windkit-1.0.2 → windkit-2.0.0}/pyproject.toml +1 -0
  6. {windkit-1.0.2 → windkit-2.0.0}/setup.cfg +24 -23
  7. {windkit-1.0.2 → windkit-2.0.0}/windkit/__init__.py +12 -2
  8. {windkit-1.0.2 → windkit-2.0.0}/windkit/_version.py +16 -3
  9. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/__init__.py +1 -1
  10. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/_colormaps.py +17 -16
  11. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/elevation_map.py +2 -10
  12. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/landcover_map.py +5 -13
  13. windkit-2.0.0/windkit/plot/time_series.py +254 -0
  14. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_bbox.py +1 -5
  15. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_interpolation.py +27 -27
  16. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_point.py +25 -30
  17. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/spatial.py +1 -1
  18. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/map_conversions.py +10 -7
  19. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/vector_map.py +6 -2
  20. {windkit-1.0.2 → windkit-2.0.0}/windkit/tutorial_data.py +4 -1
  21. {windkit-1.0.2 → windkit-2.0.0}/windkit/weibull.py +30 -15
  22. windkit-2.0.0/windkit/wind.py +968 -0
  23. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_climate/binned_wind_climate.py +19 -5
  24. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_climate/generalized_wind_climate.py +0 -149
  25. windkit-2.0.0/windkit/wind_climate/geostrophic_wind_climate.py +178 -0
  26. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_climate/time_series_wind_climate.py +22 -0
  27. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_climate/wind_climate.py +48 -4
  28. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_data.py +2 -2
  29. windkit-2.0.0/windkit/wind_farm/power_curve.py +124 -0
  30. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_farm/uncertainty.py +4 -2
  31. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_farm/wind_turbine_generator.py +41 -0
  32. {windkit-1.0.2 → windkit-2.0.0}/windkit/xarray_structures/metadata.py +13 -7
  33. {windkit-1.0.2 → windkit-2.0.0}/windkit.egg-info/SOURCES.txt +3 -0
  34. windkit-1.0.2/PKG-INFO +0 -93
  35. windkit-1.0.2/README.md +0 -45
  36. windkit-1.0.2/windkit/wind.py +0 -469
  37. windkit-1.0.2/windkit/wind_climate/geostrophic_wind_climate.py +0 -75
  38. {windkit-1.0.2 → windkit-2.0.0}/LICENSE +0 -0
  39. {windkit-1.0.2 → windkit-2.0.0}/windkit/_errors.py +0 -0
  40. {windkit-1.0.2 → windkit-2.0.0}/windkit/_rvea_xml.py +0 -0
  41. {windkit-1.0.2 → windkit-2.0.0}/windkit/_weibull_nb.py +0 -0
  42. {windkit-1.0.2 → windkit-2.0.0}/windkit/cfd.py +0 -0
  43. {windkit-1.0.2 → windkit-2.0.0}/windkit/config.py +0 -0
  44. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/all_vars.json +0 -0
  45. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/color_table_roughness.json +0 -0
  46. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/landcover_tables/CGLS-LC100.json +0 -0
  47. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/landcover_tables/CORINE.json +0 -0
  48. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/landcover_tables/ESA_CCI.json +0 -0
  49. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/landcover_tables/GWA4_micro.json +0 -0
  50. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/landcover_tables/Globcover.json +0 -0
  51. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/landcover_tables/MODIS.json +0 -0
  52. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/landcover_tables/NEWA_micro_v2.json +0 -0
  53. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/landcover_tables/WorldCover.json +0 -0
  54. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/loss_tables/dtu_loss_table.csv +0 -0
  55. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/map_file_header_to_epsg.json +0 -0
  56. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/map_file_header_to_epsg_read.json +0 -0
  57. {windkit-1.0.2 → windkit-2.0.0}/windkit/data/uncertainty_tables/dtu_uncertainty_table.csv +0 -0
  58. {windkit-1.0.2 → windkit-2.0.0}/windkit/import_manager.py +0 -0
  59. {windkit-1.0.2 → windkit-2.0.0}/windkit/integrations/__init__.py +0 -0
  60. {windkit-1.0.2 → windkit-2.0.0}/windkit/integrations/_earth_engine.py +0 -0
  61. {windkit-1.0.2 → windkit-2.0.0}/windkit/integrations/_gwa_map_api.py +0 -0
  62. {windkit-1.0.2 → windkit-2.0.0}/windkit/integrations/_planetary_computer.py +0 -0
  63. {windkit-1.0.2 → windkit-2.0.0}/windkit/ltc/__init__.py +0 -0
  64. {windkit-1.0.2 → windkit-2.0.0}/windkit/ltc/_validation.py +0 -0
  65. {windkit-1.0.2 → windkit-2.0.0}/windkit/ltc/mcp.py +0 -0
  66. {windkit-1.0.2 → windkit-2.0.0}/windkit/ltc/regression.py +0 -0
  67. {windkit-1.0.2 → windkit-2.0.0}/windkit/ltc/scores.py +0 -0
  68. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/_helpers.py +0 -0
  69. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/color.py +0 -0
  70. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/histogram.py +0 -0
  71. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/operational_curves.py +0 -0
  72. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/raster_plot.py +0 -0
  73. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/roughness_rose.py +0 -0
  74. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/vertical_profile.py +0 -0
  75. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/wind_rose.py +0 -0
  76. {windkit-1.0.2 → windkit-2.0.0}/windkit/plot/wind_turbine.py +0 -0
  77. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/README.md +0 -0
  78. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/__init__.py +0 -0
  79. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_crs.py +0 -0
  80. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_cuboid.py +0 -0
  81. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_dimensions.py +0 -0
  82. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_latitude.py +0 -0
  83. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_metpy_natural_neighbors.py +0 -0
  84. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_raster.py +0 -0
  85. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_struct.py +0 -0
  86. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_utm.py +0 -0
  87. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_vector.py +0 -0
  88. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/_vertical.py +0 -0
  89. {windkit-1.0.2 → windkit-2.0.0}/windkit/spatial/decorators.py +0 -0
  90. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/__init__.py +0 -0
  91. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/_vectormap_gml.py +0 -0
  92. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/_vectormap_helpers.py +0 -0
  93. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/get_map.py +0 -0
  94. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/landcover.py +0 -0
  95. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/map_conversion/__init__.py +0 -0
  96. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/map_conversion/_polygons_constraints.py +0 -0
  97. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/map_conversion/helper_functions.py +0 -0
  98. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/map_conversion/lines_to_poly.py +0 -0
  99. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/map_conversion/poly_to_lines.py +0 -0
  100. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/raster_map.py +0 -0
  101. {windkit-1.0.2 → windkit-2.0.0}/windkit/topography/topography.py +0 -0
  102. {windkit-1.0.2 → windkit-2.0.0}/windkit/utils.py +0 -0
  103. {windkit-1.0.2 → windkit-2.0.0}/windkit/weng_workspace.py +0 -0
  104. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_climate/__init__.py +0 -0
  105. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_climate/predicted_wind_climate.py +0 -0
  106. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_climate/weibull_wind_climate.py +0 -0
  107. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_farm/__init__.py +0 -0
  108. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_farm/loss.py +0 -0
  109. {windkit-1.0.2 → windkit-2.0.0}/windkit/wind_farm/wind_turbines.py +0 -0
  110. {windkit-1.0.2 → windkit-2.0.0}/windkit/workspace.py +0 -0
  111. {windkit-1.0.2 → windkit-2.0.0}/windkit/xarray_structures/_validate.py +0 -0
  112. {windkit-1.0.2 → windkit-2.0.0}/windkit/xarray_structures/data_structures.py +0 -0
  113. {windkit-1.0.2 → windkit-2.0.0}/windkit/xarray_structures/direction.py +0 -0
  114. {windkit-1.0.2 → windkit-2.0.0}/windkit/xarray_structures/empty.py +0 -0
  115. {windkit-1.0.2 → windkit-2.0.0}/windkit/xarray_structures/sector.py +0 -0
  116. {windkit-1.0.2 → windkit-2.0.0}/windkit/xarray_structures/wsbin.py +0 -0
@@ -2,7 +2,46 @@
2
2
 
3
3
  All major changes are listed here.
4
4
 
5
- ## 1.0.2 (2025-07-21)
5
+ ## 2.0.0 (2025-07-21)
6
+
7
+ ### Highlights
8
+
9
+ ### Performance improvements
10
+ * Significantly reduced import time by lazy loading some packages and data. Should be approx 1/3 of previous import time.
11
+
12
+ ### Sheer and veer functionality
13
+
14
+ * Added new function `wk.shear_extrapolate` to extrapolate wind speeds to different heights using a power law shear profile.
15
+ * Added new function `wk.shear_exponent` to calculate a shear exponent from wind speeds
16
+ * Added guidance about `wk.shear_exponent` into shear extrapolation example.
17
+
18
+ * Added new function `wk.veer_extrapolate` to extrapolate wind direction linearly to different heights, accounting for circular discontinuities.
19
+ * Added new function `wk.wind_veer` to calculate wind veer from wind direction
20
+ * Added guidance about `wk.wind_veer` into shear extrapolation example.
21
+
22
+ ### Support for PyWAsP's Geostrophic wind climate objects
23
+
24
+ * new function `create_geowc`, which creates a dummy geostrophic wind climate
25
+ * functions `mean_wind_speed`, `mean_power_density`, `ws_freq_gt_mean`, `ws_cdf` and `mean_ws_moment` are now also working when a geostrophic wind climate is passed.
26
+ * bug fix to interpolation, which would create invalid spatial structures when a dimension was in exclude dims but not present in the target structure (e.g. when doing interpolation from `stacked_point` to `point`).
27
+
28
+ ### New features
29
+ * Added new function `wk.extrapolate_to_height` to extrapolate wind climate objects to different heights. Currently, only works for time-series wind climates (TSWC).
30
+ * Fixed bug where a point dataset with several horizontal locations would be converted to a single horizontal location when using the function `to_stacked_point`
31
+ * `wk.roughness_map_to_file` now allows to write to a old fashioned `.map` file for the special case where these is a single polygon and the external roughness length is not specified (None). For this case we assume that the external roughness length is the same as the roughness length inside the polygon.
32
+ * `wk.roughness_map_to_file` now allows users to write to a `.map` as long all displacement heights in the map are zero.
33
+ * Bugfix in `read_bwc`: it now opens `bwc` stored in netcdf correctly.
34
+ but some of this functionality was only available in pywasp, so it has been moved there.
35
+ * For `wv_count` objects (thus also geostrophic wind climates) if all of the bins have nan's, the result of function `mean_wind_speed`, `mean_power_density`, `ws_freq_gt_mean` and `mean_ws_moment` operating on such a function is also nan, whereas before it would be 0.
36
+
37
+ ### Changes
38
+ * Changed default value of `use_bounds` to `True` in `wk.spatial.BBox.reproject` method.
39
+
40
+ ### Breaking changes
41
+
42
+ * `spatial_interpolate_gwc` has been removed. This functionality is now only available in PyWAsP through its `interpolate_gwc` function.
43
+
44
+ ## [1.0.2 (2025-07-21)](https://docs.wasp.dk/windkit/v1.0.2/)
6
45
 
7
46
  WindKit 1.0.2 is a patch release to ensure consistent config and app data paths on windows. Both the config and app data paths are now set by default to ``C:\\Users\\<user>\\AppData\\Roaming\\DTU Wind Energy\\windkit``.
8
47
 
@@ -277,7 +316,7 @@ as required in the `stacked_point` structure.
277
316
  * Switched configuration file from ini to toml, old file will be automatically migrated
278
317
 
279
318
 
280
- ## 0.8.0 (2024-06-10)
319
+ ## [0.8.0 (2024-06-10)](https://docs.wasp.dk/windkit/v0.8.0/)
281
320
 
282
321
  ### New features
283
322
 
@@ -0,0 +1,294 @@
1
+ # Performance Regression CI Job - Implementation Plan
2
+
3
+ ## Overview
4
+
5
+ This plan outlines the implementation of a CI job to catch performance regressions in windkit. The job will test import timing, function execution benchmarks, and memory usage.
6
+
7
+ ## Goals
8
+
9
+ 1. **Catch regressions** in import time (target: < 1 second)
10
+ 2. **Benchmark critical functions** and track performance over time
11
+ 3. **Fail CI** when performance degrades beyond acceptable thresholds
12
+ 4. **Provide actionable reports** showing what regressed and by how much
13
+
14
+ ---
15
+
16
+ ## Implementation Components
17
+
18
+ ### 1. Performance Test Module
19
+
20
+ **File: `test/test_performance.py`**
21
+
22
+ ```python
23
+ """Performance regression tests for windkit."""
24
+
25
+ import sys
26
+ import time
27
+ import pytest
28
+
29
+ # Thresholds (in seconds)
30
+ IMPORT_TIME_THRESHOLD = 1.0 # Maximum acceptable import time
31
+ IMPORT_TIME_WARNING = 0.8 # Warning threshold
32
+
33
+
34
+ class TestImportPerformance:
35
+ """Tests for import time performance."""
36
+
37
+ def test_import_time_under_threshold(self):
38
+ """Verify windkit imports in under 1 second."""
39
+ # Ensure windkit is not already imported
40
+ modules_to_remove = [k for k in sys.modules if k.startswith('windkit')]
41
+ for mod in modules_to_remove:
42
+ del sys.modules[mod]
43
+
44
+ start = time.perf_counter()
45
+ import windkit # noqa: F401
46
+ elapsed = time.perf_counter() - start
47
+
48
+ assert elapsed < IMPORT_TIME_THRESHOLD, (
49
+ f"Import time {elapsed:.3f}s exceeds threshold {IMPORT_TIME_THRESHOLD}s"
50
+ )
51
+
52
+ def test_lazy_imports_not_loaded(self):
53
+ """Verify heavy optional dependencies are not loaded on import."""
54
+ modules_to_remove = [k for k in sys.modules if k.startswith('windkit')]
55
+ for mod in modules_to_remove:
56
+ del sys.modules[mod]
57
+
58
+ import windkit # noqa: F401
59
+
60
+ # These should NOT be loaded on import
61
+ lazy_modules = [
62
+ 'matplotlib',
63
+ 'numba',
64
+ 'scipy.stats',
65
+ 'pystac_client',
66
+ 'pydantic_settings',
67
+ ]
68
+
69
+ for mod in lazy_modules:
70
+ assert mod not in sys.modules, (
71
+ f"Module '{mod}' should not be loaded on 'import windkit'"
72
+ )
73
+
74
+
75
+ @pytest.mark.slow
76
+ class TestFunctionBenchmarks:
77
+ """Benchmark tests for critical functions."""
78
+
79
+ def test_create_bwc_performance(self, benchmark_output_locs):
80
+ """Benchmark create_bwc function."""
81
+ import windkit as wk
82
+
83
+ start = time.perf_counter()
84
+ wk.create_bwc(benchmark_output_locs, n_sectors=12, n_wsbins=30)
85
+ elapsed = time.perf_counter() - start
86
+
87
+ # Log timing for tracking
88
+ print(f"create_bwc: {elapsed:.4f}s")
89
+
90
+ # Threshold TBD based on baseline measurements
91
+ assert elapsed < 5.0, f"create_bwc took {elapsed:.3f}s, exceeds threshold"
92
+
93
+ def test_spatial_operations_performance(self, benchmark_dataset):
94
+ """Benchmark spatial operations."""
95
+ import windkit as wk
96
+
97
+ start = time.perf_counter()
98
+ wk.spatial.to_point(benchmark_dataset)
99
+ elapsed = time.perf_counter() - start
100
+
101
+ print(f"to_point: {elapsed:.4f}s")
102
+ assert elapsed < 1.0, f"to_point took {elapsed:.3f}s, exceeds threshold"
103
+ ```
104
+
105
+ ### 2. Benchmark Fixtures
106
+
107
+ **File: `test/fixtures/fixture_benchmarks.py`**
108
+
109
+ ```python
110
+ """Fixtures for performance benchmarks."""
111
+
112
+ import pytest
113
+ import numpy as np
114
+
115
+
116
+ @pytest.fixture(scope="module")
117
+ def benchmark_output_locs():
118
+ """Create a standard dataset for benchmarking."""
119
+ import windkit as wk
120
+
121
+ return wk.spatial.create_point(
122
+ x=[0, 1, 2],
123
+ y=[0, 1, 2],
124
+ z=[10, 20, 30],
125
+ crs=4326
126
+ )
127
+
128
+
129
+ @pytest.fixture(scope="module")
130
+ def benchmark_dataset():
131
+ """Create a larger dataset for spatial benchmarks."""
132
+ import windkit as wk
133
+
134
+ # Create a 100x100 grid for meaningful benchmarks
135
+ x = np.linspace(0, 100, 50)
136
+ y = np.linspace(0, 100, 50)
137
+
138
+ return wk.spatial.create_raster(
139
+ x=x,
140
+ y=y,
141
+ crs=32632
142
+ )
143
+ ```
144
+
145
+ ### 3. pytest-benchmark Integration (Optional Enhancement)
146
+
147
+ **Add to `pixi.toml` feature.dev.dependencies:**
148
+
149
+ ```toml
150
+ [feature.dev.dependencies]
151
+ pytest-benchmark = "*"
152
+ ```
153
+
154
+ **Enhanced benchmark test using pytest-benchmark:**
155
+
156
+ ```python
157
+ def test_import_time_benchmark(benchmark):
158
+ """Benchmark import time with pytest-benchmark."""
159
+ def import_windkit():
160
+ modules = [k for k in sys.modules if k.startswith('windkit')]
161
+ for mod in modules:
162
+ del sys.modules[mod]
163
+ import windkit
164
+ return windkit
165
+
166
+ result = benchmark(import_windkit)
167
+ assert result is not None
168
+ ```
169
+
170
+ ### 4. CI Job Configuration
171
+
172
+ **Add to `.gitlab-ci.yml`:**
173
+
174
+ ```yaml
175
+ # Performance regression tests
176
+ performance_tests:
177
+ extends:
178
+ - .pixi_pytest
179
+ stage: test
180
+ before_script:
181
+ - mkdir -p ~/.config/windkit/
182
+ - cp .conda_env/windkit.ini ~/.config/windkit/windkit.ini
183
+ variables:
184
+ PIXI_ENV: "default"
185
+ script:
186
+ - pixi run -e $PIXI_ENV test-performance
187
+ artifacts:
188
+ when: always
189
+ paths:
190
+ - performance_results/
191
+ reports:
192
+ junit: performance_junit.xml
193
+ expire_in: 1 month
194
+ rules:
195
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
196
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
197
+ allow_failure: false # Fail the pipeline on performance regression
198
+ ```
199
+
200
+ ### 5. Pixi Task Configuration
201
+
202
+ **Add to `pixi.toml`:**
203
+
204
+ ```toml
205
+ [feature.dev.tasks]
206
+ # ... existing tasks ...
207
+
208
+ # Performance tests
209
+ test-performance = {cmd="cd test; pytest test_performance.py -v --tb=short --junitxml=../performance_junit.xml", depends-on = ["install_tests"]}
210
+
211
+ # Performance tests with benchmarks (if pytest-benchmark is added)
212
+ test-benchmark = {cmd="cd test; pytest test_performance.py -v --benchmark-only --benchmark-json=../performance_results/benchmark.json", depends-on = ["install_tests"]}
213
+ ```
214
+
215
+ ---
216
+
217
+ ## Implementation Phases
218
+
219
+ ### Phase 1: Basic Import Time Testing (Minimal)
220
+ - [ ] Create `test/test_performance.py` with import time tests
221
+ - [ ] Add `performance` marker to `pyproject.toml`
222
+ - [ ] Add `test-performance` task to `pixi.toml`
223
+ - [ ] Verify tests pass locally
224
+
225
+ ### Phase 2: CI Integration
226
+ - [ ] Add `performance_tests` job to `.gitlab-ci.yml`
227
+ - [ ] Configure artifacts for performance results
228
+ - [ ] Test in MR pipeline
229
+
230
+ ### Phase 3: Function Benchmarks (Optional)
231
+ - [ ] Add `pytest-benchmark` dependency
232
+ - [ ] Create benchmark fixtures
233
+ - [ ] Add benchmark tests for critical functions
234
+ - [ ] Store benchmark history for trend analysis
235
+
236
+ ### Phase 4: Advanced Monitoring (Future)
237
+ - [ ] Memory profiling with `pytest-memray` or `memory_profiler`
238
+ - [ ] Benchmark comparison against main branch
239
+ - [ ] Performance dashboard/visualization
240
+ - [ ] Slack/email alerts on regression
241
+
242
+ ---
243
+
244
+ ## Thresholds and Metrics
245
+
246
+ | Metric | Threshold | Warning | Action on Failure |
247
+ |--------|-----------|---------|-------------------|
248
+ | Import time | < 1.0s | < 0.8s | Block merge |
249
+ | Lazy modules loaded | 0 | - | Block merge |
250
+ | create_bwc | < 5.0s | < 3.0s | Warning |
251
+ | Spatial operations | < 1.0s | < 0.5s | Warning |
252
+
253
+ ---
254
+
255
+ ## File Changes Summary
256
+
257
+ | File | Change |
258
+ |------|--------|
259
+ | `test/test_performance.py` | New file - performance tests |
260
+ | `test/fixtures/fixture_benchmarks.py` | New file - benchmark fixtures |
261
+ | `test/conftest.py` | Import benchmark fixtures |
262
+ | `pixi.toml` | Add test-performance task |
263
+ | `.gitlab-ci.yml` | Add performance_tests job |
264
+ | `pyproject.toml` | Add performance marker |
265
+
266
+ ---
267
+
268
+ ## Questions to Consider
269
+
270
+ 1. **Should performance tests run on every MR or only on main?**
271
+ - Recommendation: Run on every MR to catch regressions before merge
272
+
273
+ 2. **Should we use pytest-benchmark for detailed tracking?**
274
+ - Recommendation: Start simple, add later if needed
275
+
276
+ 3. **What functions should be benchmarked?**
277
+ - Import time (critical)
278
+ - create_bwc, create_tswc (commonly used)
279
+ - Spatial transformations (performance-sensitive)
280
+ - File I/O operations (user-facing)
281
+
282
+ 4. **How to handle flaky timing tests?**
283
+ - Use generous thresholds (2x expected time)
284
+ - Run multiple iterations and take median
285
+ - Mark as `allow_failure` initially, tighten later
286
+
287
+ ---
288
+
289
+ ## Next Steps
290
+
291
+ 1. Review and approve this plan
292
+ 2. Implement Phase 1 (basic import time testing)
293
+ 3. Test locally and in CI
294
+ 4. Iterate based on results
windkit-2.0.0/PKG-INFO ADDED
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.4
2
+ Name: windkit
3
+ Version: 2.0.0
4
+ Summary: WindKit provides core functionalities for working with wind resource data
5
+ Home-page: https://docs.wasp.dk/windkit/latest
6
+ Author: DTU Wind Energy
7
+ License: BSD-3-Clause
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.11
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: lxml>=5.0
14
+ Requires-Dist: netcdf4>=1.6
15
+ Requires-Dist: numpy>=1.26
16
+ Requires-Dist: packaging>=23
17
+ Requires-Dist: pandas>=2.1
18
+ Requires-Dist: pyproj>=3.6
19
+ Requires-Dist: scipy>=1.12
20
+ Requires-Dist: shapely>=2
21
+ Requires-Dist: xarray>=2023.9
22
+ Requires-Dist: rioxarray>=0.17
23
+ Requires-Dist: rasterio>=1.3
24
+ Requires-Dist: geopandas>=1.0
25
+ Requires-Dist: h5netcdf>=1.3
26
+ Requires-Dist: h5py>=3.9
27
+ Requires-Dist: matplotlib>=3.8
28
+ Requires-Dist: pydantic-settings>=2.3
29
+ Requires-Dist: pydantic>=2.7
30
+ Requires-Dist: platformdirs>=4.3.8
31
+ Requires-Dist: plotly>=5
32
+ Requires-Dist: kaleido>=0.2.1
33
+ Requires-Dist: requests>=2.31
34
+ Requires-Dist: toml>=0.10
35
+ Provides-Extra: plots
36
+ Requires-Dist: matplotlib>=3.6; extra == "plots"
37
+ Requires-Dist: plotly>=5.12; extra == "plots"
38
+ Requires-Dist: kaleido>=0.2.1; extra == "plots"
39
+ Provides-Extra: data-apis
40
+ Requires-Dist: ee; extra == "data-apis"
41
+ Requires-Dist: pystac_client; extra == "data-apis"
42
+ Requires-Dist: planetary_computer; extra == "data-apis"
43
+ Requires-Dist: fsspec; extra == "data-apis"
44
+ Requires-Dist: adlfs; extra == "data-apis"
45
+ Requires-Dist: zarr; extra == "data-apis"
46
+ Provides-Extra: performance
47
+ Requires-Dist: numba; extra == "performance"
48
+ Dynamic: license-file
49
+
50
+ # WindKit
51
+
52
+ [![pipeline status](https://gitlab-internal.windenergy.dtu.dk/ram/software/pywasp/windkit/badges/master/pipeline.svg)](https://gitlab-internal.windenergy.dtu.dk/ram/software/pywasp/windkit/-/pipelines)
53
+ [![coverage](https://gitlab-internal.windenergy.dtu.dk/ram/software/pywasp/windkit/badges/master/coverage.svg)](https://gitlab-internal.windenergy.dtu.dk/ram/software/pywasp/windkit/-/pipelines)
54
+ [![release](https://gitlab-internal.windenergy.dtu.dk/ram/software/pywasp/windkit/-/badges/release.svg)](https://gitlab-internal.windenergy.dtu.dk/ram/software/pywasp/windkit/-/releases)
55
+ [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD--3--Clause-blue.svg)](LICENSE)
56
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
57
+ [![Docs](https://img.shields.io/badge/docs-docs.wasp.dk-blue?logo=readthedocs&logoColor=white)](https://docs.wasp.dk/windkit)
58
+
59
+ [![PyPI](https://img.shields.io/pypi/v/windkit)](https://pypi.org/project/windkit/)
60
+ [![Downloads-pypi](https://img.shields.io/pypi/dm/windkit)](https://pypi.org/project/windkit/)
61
+
62
+
63
+ WindKit provides core functionalities for working with wind resource data, built on top of [xarray](https://xarray.dev/) and [geopandas](https://geopandas.org/).
64
+
65
+ ## Installation
66
+
67
+ **pip:**
68
+ ```bash
69
+ pip install windkit
70
+ ```
71
+
72
+ **conda:**
73
+ ```bash
74
+ conda install -c https://conda.windenergy.dtu.dk/channel/open windkit
75
+ ```
76
+
77
+ ## Quick Start
78
+
79
+ ```python
80
+ import windkit as wk
81
+
82
+ # Read a binned wind climate
83
+ bwc = wk.read_bwc("hovsore.tab")
84
+
85
+ # Calculate mean wind speed
86
+ mean_ws = wk.mean_wind_speed(bwc)
87
+ print(f"Mean wind speed: {mean_ws.values:.2f} m/s")
88
+
89
+ # Fit Weibull distribution and convert to Weibull wind climate
90
+ wwc = wk.weibull_fit(bwc)
91
+ ```
92
+
93
+ ## Features
94
+
95
+ - **Wind Climate I/O** - Read/write binned, Weibull, generalized, and time-series wind climates (NetCDF, .lib, .tab, .rsf, .wrg)
96
+ - **Map I/O** - Read/write elevation, roughness, and landcover maps (raster and vector formats)
97
+ - **WAsP Integration** - Read WAsP Workspace files and WAsP Engineering project files
98
+ - **Spatial Processing** - Clip, reproject, and interpolate spatial data with full CRS support
99
+ - **Wind Statistics** - Calculate mean wind speed, power density, Weibull parameters, and more
100
+ - **Plotting** - Interactive plots with Plotly, static plots with Matplotlib
101
+ - **Remote Data Access** - Download ERA5 reanalysis data and satellite-derived maps
102
+
103
+ ## Documentation
104
+
105
+ Full documentation: [docs.wasp.dk/windkit](https://docs.wasp.dk/windkit)
106
+
107
+ Community forum: [WAsP Python tools](https://www.wasptechnical.dk/forum/forum/19-wasp-python-tools/)
108
+
109
+ ## Contributing
110
+
111
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
112
+
113
+ ## License
114
+
115
+ BSD-3-Clause - see [LICENSE](LICENSE) for details.
@@ -0,0 +1,66 @@
1
+ # WindKit
2
+
3
+ [![pipeline status](https://gitlab-internal.windenergy.dtu.dk/ram/software/pywasp/windkit/badges/master/pipeline.svg)](https://gitlab-internal.windenergy.dtu.dk/ram/software/pywasp/windkit/-/pipelines)
4
+ [![coverage](https://gitlab-internal.windenergy.dtu.dk/ram/software/pywasp/windkit/badges/master/coverage.svg)](https://gitlab-internal.windenergy.dtu.dk/ram/software/pywasp/windkit/-/pipelines)
5
+ [![release](https://gitlab-internal.windenergy.dtu.dk/ram/software/pywasp/windkit/-/badges/release.svg)](https://gitlab-internal.windenergy.dtu.dk/ram/software/pywasp/windkit/-/releases)
6
+ [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD--3--Clause-blue.svg)](LICENSE)
7
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
8
+ [![Docs](https://img.shields.io/badge/docs-docs.wasp.dk-blue?logo=readthedocs&logoColor=white)](https://docs.wasp.dk/windkit)
9
+
10
+ [![PyPI](https://img.shields.io/pypi/v/windkit)](https://pypi.org/project/windkit/)
11
+ [![Downloads-pypi](https://img.shields.io/pypi/dm/windkit)](https://pypi.org/project/windkit/)
12
+
13
+
14
+ WindKit provides core functionalities for working with wind resource data, built on top of [xarray](https://xarray.dev/) and [geopandas](https://geopandas.org/).
15
+
16
+ ## Installation
17
+
18
+ **pip:**
19
+ ```bash
20
+ pip install windkit
21
+ ```
22
+
23
+ **conda:**
24
+ ```bash
25
+ conda install -c https://conda.windenergy.dtu.dk/channel/open windkit
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```python
31
+ import windkit as wk
32
+
33
+ # Read a binned wind climate
34
+ bwc = wk.read_bwc("hovsore.tab")
35
+
36
+ # Calculate mean wind speed
37
+ mean_ws = wk.mean_wind_speed(bwc)
38
+ print(f"Mean wind speed: {mean_ws.values:.2f} m/s")
39
+
40
+ # Fit Weibull distribution and convert to Weibull wind climate
41
+ wwc = wk.weibull_fit(bwc)
42
+ ```
43
+
44
+ ## Features
45
+
46
+ - **Wind Climate I/O** - Read/write binned, Weibull, generalized, and time-series wind climates (NetCDF, .lib, .tab, .rsf, .wrg)
47
+ - **Map I/O** - Read/write elevation, roughness, and landcover maps (raster and vector formats)
48
+ - **WAsP Integration** - Read WAsP Workspace files and WAsP Engineering project files
49
+ - **Spatial Processing** - Clip, reproject, and interpolate spatial data with full CRS support
50
+ - **Wind Statistics** - Calculate mean wind speed, power density, Weibull parameters, and more
51
+ - **Plotting** - Interactive plots with Plotly, static plots with Matplotlib
52
+ - **Remote Data Access** - Download ERA5 reanalysis data and satellite-derived maps
53
+
54
+ ## Documentation
55
+
56
+ Full documentation: [docs.wasp.dk/windkit](https://docs.wasp.dk/windkit)
57
+
58
+ Community forum: [WAsP Python tools](https://www.wasptechnical.dk/forum/forum/19-wasp-python-tools/)
59
+
60
+ ## Contributing
61
+
62
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
63
+
64
+ ## License
65
+
66
+ BSD-3-Clause - see [LICENSE](LICENSE) for details.
@@ -11,4 +11,5 @@ norecursedirs = "dist build .tox"
11
11
  testpaths = ["test",]
12
12
  markers = [
13
13
  "slow: marks tests as slow (deselect with '-m \"not slow\"')",
14
+ "performance: marks tests as performance tests (run with '-m performance')",
14
15
  ]
@@ -9,34 +9,35 @@ license_files = LICENSE
9
9
  classifiers =
10
10
  Programming Language :: Python :: 3
11
11
  Operating System :: OS Independent
12
- url = https://docs-wasp.ramtt.xyz/windkit
12
+ url = https://docs.wasp.dk/windkit/latest
13
13
 
14
14
  [options]
15
15
  packages = find_namespace:
16
- python_requires = >=3.11,<4
16
+ python_requires = >=3.11
17
17
  include_package_data = True
18
18
  install_requires =
19
- lxml >=5.0,<6
20
- netcdf4 >=1.6,<2
21
- numpy >=1.26,<3
19
+ lxml >=5.0
20
+ netcdf4 >=1.6
21
+ numpy >=1.26
22
22
  packaging >=23
23
- pandas >=2.1,<3
24
- pyproj >=3.6,<4
25
- scipy >=1.12,<2
26
- shapely >=2,<3
27
- xarray >=2023.9,<2025.7
28
- rioxarray >=0.17,<0.20
29
- rasterio >=1.3,<2
30
- geopandas >=1.0,<1.2
31
- h5netcdf >=1.3,<2
32
- matplotlib >=3.8,<4
33
- pydantic-settings >=2.3,<3
34
- pydantic >=2.7,<3
35
- platformdirs >=4.3.8,<5
36
- plotly >=5,<6
37
- kaleido >=0.2.1,<0.3
38
- requests >=2.31,<3
39
- toml >=0.10,<0.11
23
+ pandas >=2.1
24
+ pyproj >=3.6
25
+ scipy >=1.12
26
+ shapely >=2
27
+ xarray >=2023.9
28
+ rioxarray >=0.17
29
+ rasterio >=1.3
30
+ geopandas >=1.0
31
+ h5netcdf >=1.3
32
+ h5py >=3.9
33
+ matplotlib >=3.8
34
+ pydantic-settings >=2.3
35
+ pydantic >=2.7
36
+ platformdirs >=4.3.8
37
+ plotly >=5
38
+ kaleido >=0.2.1
39
+ requests >=2.31
40
+ toml >=0.10
40
41
 
41
42
  [options.package_data]
42
43
  windkit.data =
@@ -51,7 +52,7 @@ windkit.data.uncertainty_tables =
51
52
  *.csv
52
53
 
53
54
  [options.extras_require]
54
- plots = matplotlib>=3.6; plotly>=5.12; kaleido>=0.2
55
+ plots = matplotlib>=3.6; plotly>=5.12; kaleido>=0.2.1
55
56
  data_apis = ee; pystac_client; planetary_computer; fsspec; adlfs; zarr
56
57
  performance = numba
57
58
 
@@ -20,6 +20,10 @@ __all__ = [
20
20
  "vinterp_wind_speed",
21
21
  "rotor_equivalent_wind_speed",
22
22
  "tswc_resample",
23
+ "shear_extrapolate",
24
+ "shear_exponent",
25
+ "veer_extrapolate",
26
+ "wind_veer",
23
27
  # Time Series Wind Climate
24
28
  "validate_tswc",
25
29
  "is_tswc",
@@ -50,10 +54,10 @@ __all__ = [
50
54
  "create_gwc",
51
55
  "read_gwc",
52
56
  "gwc_to_file",
53
- "spatial_interpolate_gwc",
54
57
  # Geostrophic wind climate
55
58
  "validate_geowc",
56
59
  "is_geowc",
60
+ "create_geowc",
57
61
  # Predicted Wind Climate
58
62
  "create_pwc",
59
63
  # Wind Climate
@@ -64,6 +68,7 @@ __all__ = [
64
68
  "mean_wind_speed",
65
69
  "mean_power_density",
66
70
  "get_cross_predictions",
71
+ "extrapolate_to_height",
67
72
  # Landcover
68
73
  "LandCoverTable",
69
74
  "get_landcover_table",
@@ -153,6 +158,10 @@ from windkit.wind import (
153
158
  vinterp_wind_speed,
154
159
  rotor_equivalent_wind_speed,
155
160
  tswc_resample,
161
+ shear_extrapolate,
162
+ shear_exponent,
163
+ veer_extrapolate,
164
+ wind_veer,
156
165
  )
157
166
  from windkit.wind_climate.time_series_wind_climate import (
158
167
  validate_tswc,
@@ -190,13 +199,13 @@ from windkit.wind_climate.generalized_wind_climate import (
190
199
  create_gwc,
191
200
  read_gwc,
192
201
  gwc_to_file,
193
- spatial_interpolate_gwc,
194
202
  _reproject_gwc,
195
203
  )
196
204
  from windkit.wind_climate import generalized_wind_climate # noqa: F401
197
205
  from windkit.wind_climate.geostrophic_wind_climate import (
198
206
  validate_geowc,
199
207
  is_geowc,
208
+ create_geowc,
200
209
  )
201
210
  from windkit.wind_climate import geostrophic_wind_climate # noqa: F401
202
211
  from windkit.wind_climate.predicted_wind_climate import create_pwc
@@ -209,6 +218,7 @@ from windkit.wind_climate.wind_climate import (
209
218
  mean_wind_speed,
210
219
  mean_power_density,
211
220
  get_cross_predictions,
221
+ extrapolate_to_height,
212
222
  )
213
223
  from windkit.cfd import read_cfdres
214
224
  from windkit.wind_farm.wind_turbine_generator import (