1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
//! # wind-profile
//!
//! Vertical wind-profile extrapolation for wind energy: take the wind a weather model
//! gives you at its fixed output heights and move it to the height a turbine actually
//! harvests at, then correct for what the air weighs when it gets there.
//!
//! ## Why hub height is a first-class problem
//!
//! Numerical weather models publish wind at a handful of fixed heights: 10 m always,
//! and depending on the model an 80 m or 100 m level (GFS carries 80 m, the ECMWF open
//! data carries 100 m). Modern turbine hubs sit at 80-160 m and climbing. Turbine power
//! goes with the cube of wind speed below rated, so a relative error in extrapolated
//! speed shows up three times as large in energy: 5% short on wind is roughly 15% short
//! on generation. The vertical profile is not a rounding step, it is where the money is.
//!
//! ## Which law, when
//!
//! - **Power law**, [`wind_speed_m_s_power_law`]: the wind-energy engineering standard,
//! `u(z) = u_ref * (z / z_ref)^alpha`. Use [`shear_exponent_from_two_levels`] to derive
//! `alpha` from two model heights when the model provides them; that locality beats any
//! tabulated exponent. With only one height, [`SHEAR_EXPONENT_ONE_SEVENTH`] is the
//! classic neutral-onshore default (offshore shear is typically nearer 0.10).
//! - **Log law**, [`wind_speed_m_s_log_law`]: the surface-layer similarity form,
//! `u(z) = u_ref * ln(z / z0) / ln(z_ref / z0)`. Use it when you know the terrain and
//! can pick a roughness length; the `Z0_*` constants carry the Davenport-Wieringa
//! classification from open sea to city centre.
//!
//! Both are neutral-stability forms, the honest baseline when all you have is model
//! output. Diabatic (Monin-Obukhov) corrections need surface-flux inputs the target user
//! rarely has and are deliberately out of scope for now.
//!
//! ```
//! use wind_profile as wp;
//!
//! // A GFS-style pair: 6.2 m/s at 10 m, 8.9 m/s at 80 m. Derive the local shear...
//! let alpha = wp::shear_exponent_from_two_levels(6.2, 10.0, 8.9, 80.0);
//! assert!((alpha - 0.1738).abs() < 1e-3);
//!
//! // ...and carry the 80 m wind up to a 120 m hub.
//! let u_hub = wp::wind_speed_m_s_power_law(8.9, 80.0, 120.0, alpha);
//! assert!((u_hub - 9.55).abs() < 0.01);
//! ```
//!
//! ## Air density, the other half of the power calculation
//!
//! Power also scales linearly with air density, and density at a warm low-pressure site
//! is easily 10% off the 1.225 kg/m^3 reference that power curves are quoted at.
//! [`air_density_kg_m3`] is the IEC 61400-12-1 equation (humidity-corrected, via the
//! standard's own vapor-pressure fit), and [`density_corrected_wind_speed_m_s`] is the
//! standard's `(rho / rho_ref)^(1/3)` wind-speed normalization that makes a measured
//! speed comparable against a reference-density power curve.
//!
//! ```
//! use wind_profile as wp;
//!
//! // Dry air at ISA sea level is the IEC reference density exactly.
//! let rho = wp::air_density_kg_m3(288.15, 101_325.0, 0.0);
//! assert!((rho - wp::REFERENCE_AIR_DENSITY_KG_M3).abs() < 1e-3);
//! ```
//!
//! Units are explicit in every function name: `_m_s` metres per second, `_m` metres,
//! `_k` kelvin, `_pa` pascals, `_kg_m3` kilograms per cubic metre. Inputs are physical
//! quantities (heights above ground positive and above the roughness length, speeds
//! positive where a ratio is taken); outside that domain the arithmetic yields NaN or
//! infinity rather than a guess. No dependencies, no `unsafe`.
/// Specific gas constant of dry air (J/(kg*K)), the value fixed by IEC 61400-12-1.
pub const R_DRY_AIR: f64 = 287.05;
/// Specific gas constant of water vapor (J/(kg*K)), per IEC 61400-12-1.
pub const R_WATER_VAPOR: f64 = 461.5;
/// The reference air density power curves are quoted at (kg/m^3): ISA sea level.
pub const REFERENCE_AIR_DENSITY_KG_M3: f64 = 1.225;
/// The classic neutral-stability onshore shear exponent, 1/7. A default for when the
/// model gives only one wind height; prefer [`shear_exponent_from_two_levels`] whenever
/// two heights exist. Offshore shear is typically nearer 0.10.
pub const SHEAR_EXPONENT_ONE_SEVENTH: f64 = 1.0 / 7.0;
/// Roughness length (m), Davenport-Wieringa class 1 "sea": open water, tidal flat.
pub const Z0_SEA_M: f64 = 0.0002;
/// Roughness length (m), Davenport-Wieringa class 2 "smooth": featureless land, ice.
pub const Z0_SMOOTH_M: f64 = 0.005;
/// Roughness length (m), Davenport-Wieringa class 3 "open": flat grassland, few obstacles.
pub const Z0_OPEN_M: f64 = 0.03;
/// Roughness length (m), Davenport-Wieringa class 4 "roughly open": low crops, scattered obstacles.
pub const Z0_ROUGHLY_OPEN_M: f64 = 0.10;
/// Roughness length (m), Davenport-Wieringa class 5 "rough": high crops, obstacle rows.
pub const Z0_ROUGH_M: f64 = 0.25;
/// Roughness length (m), Davenport-Wieringa class 6 "very rough": orchards, bushland.
pub const Z0_VERY_ROUGH_M: f64 = 0.5;
/// Roughness length (m), Davenport-Wieringa class 7 "closed": forest, suburb.
pub const Z0_CLOSED_M: f64 = 1.0;
/// Roughness length (m), Davenport-Wieringa class 8 "chaotic": city centre, high-rise.
pub const Z0_CHAOTIC_M: f64 = 2.0;
/// The shear exponent `alpha` implied by wind speeds observed at two heights:
/// `alpha = ln(u_hi / u_lo) / ln(z_hi / z_lo)`.
///
/// This is the local, in-the-moment exponent, and with modern model output (10 m plus
/// 80 m or 100 m) it beats any climatological default: it carries the actual stability
/// and terrain of the hour. Feed it straight into [`wind_speed_m_s_power_law`].
///
/// Both speeds and both heights must be positive, with the heights distinct; the
/// exponent is meaningful (and the profile monotone) when both speeds sit on the same
/// side of the profile, the overwhelmingly common case.
/// Wind speed at height `z_m` by the power law: `u_ref * (z / z_ref)^alpha`.
///
/// The wind-energy engineering standard for vertical extrapolation. `shear_exponent`
/// comes from [`shear_exponent_from_two_levels`] when the model provides two heights,
/// or [`SHEAR_EXPONENT_ONE_SEVENTH`] as the neutral onshore default. Heights are above
/// ground and must be positive; at `z_m == z_ref_m` the input speed comes back exactly.
/// Wind speed at height `z_m` by the neutral log law:
/// `u_ref * ln(z / z0) / ln(z_ref / z0)`.
///
/// The surface-layer similarity profile over terrain of roughness length
/// `roughness_length_m` (pick a `Z0_*` constant, or supply a site value). Valid for
/// heights well above the roughness length, under near-neutral stratification. Both
/// heights must exceed `roughness_length_m`, which must be positive; at
/// `z_m == z_ref_m` the input speed comes back exactly.
/// Air density (kg/m^3) from temperature (K), pressure (Pa), and relative humidity
/// (0 to 1), by the IEC 61400-12-1 equation.
///
/// `rho = (1/T) * (B/R0 - phi * Pw * (1/R0 - 1/Rw))`, with `Pw` the standard's own
/// vapor-pressure fit `0.0000205 * exp(0.0631846 * T)` (Pa). Humid air is lighter than
/// dry air at the same state, so the correction always lowers density. With
/// `relative_humidity = 0` this reduces to the dry ideal-gas law.
///
/// Feed the temperature and pressure at hub height. The fit targets ordinary surface
/// meteorology (roughly 258-313 K); it is not a general psychrometric model.
/// Wind speed normalized to a reference air density, per IEC 61400-12-1:
/// `u * (rho / rho_ref)^(1/3)`.
///
/// Turbine power scales with `rho * u^3`, so a measured speed in air of density
/// `rho_kg_m3` delivers the power that a speed of this value would deliver at
/// `rho_ref_kg_m3`. Normalize site wind with this before reading a power curve quoted
/// at [`REFERENCE_AIR_DENSITY_KG_M3`]. Thin air (lower density) corrects the speed
/// downward.