use crate::renewable::wind::turbine::{air_density, WindTurbine};
use crate::renewable::wind::wake::{farm_wake_speeds, TurbinePosition, WakeSource};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindFarm {
pub turbine: WindTurbine,
pub positions: Vec<TurbinePosition>,
pub ct_rated: f64,
pub wake_k: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FarmState {
pub hub_speeds: Vec<f64>,
pub turbine_powers_kw: Vec<f64>,
pub total_power_kw: f64,
pub wake_loss_fraction: f64,
pub capacity_factor: f64,
}
impl WindFarm {
pub fn regular_grid(
turbine: WindTurbine,
n_rows: usize,
n_cols: usize,
spacing_d: f64,
ct_rated: f64,
wake_k: f64,
) -> Self {
let d = turbine.rotor_diameter_m;
let mut positions = Vec::with_capacity(n_rows * n_cols);
for r in 0..n_rows {
for c in 0..n_cols {
positions.push(TurbinePosition {
x: r as f64 * spacing_d * d,
y: c as f64 * spacing_d * d,
});
}
}
Self {
turbine,
positions,
ct_rated,
wake_k,
}
}
pub fn n_turbines(&self) -> usize {
self.positions.len()
}
pub fn rated_power_kw(&self) -> f64 {
self.turbine.rated_power_kw * self.n_turbines() as f64
}
pub fn compute(
&self,
u_inf: f64,
wind_dir_deg: f64,
altitude_m: f64,
temp_c: f64,
) -> FarmState {
let rho = air_density(altitude_m, temp_c);
let n = self.n_turbines();
let sources: Vec<WakeSource> = (0..n)
.map(|_| WakeSource {
u_inf,
d: self.turbine.rotor_diameter_m,
ct: self.ct_rated,
k: self.wake_k,
})
.collect();
let hub_speeds = farm_wake_speeds(&self.positions, wind_dir_deg, &sources);
let turbine_powers_kw: Vec<f64> = hub_speeds
.iter()
.map(|&v| self.turbine.compute(v, rho).power_kw)
.collect();
let total_power_kw = turbine_powers_kw.iter().sum();
let no_wake_power = self.turbine.compute(u_inf, rho).power_kw * n as f64;
let wake_loss_fraction = if no_wake_power > 0.0 {
1.0 - total_power_kw / no_wake_power
} else {
0.0
};
let capacity_factor = total_power_kw / self.rated_power_kw().max(1.0);
FarmState {
hub_speeds,
turbine_powers_kw,
total_power_kw,
wake_loss_fraction,
capacity_factor,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::renewable::wind::turbine::WindTurbine;
fn small_farm() -> WindFarm {
WindFarm::regular_grid(WindTurbine::iec_2mw(), 2, 2, 7.0, 0.8, 0.04)
}
#[test]
fn test_farm_n_turbines() {
assert_eq!(small_farm().n_turbines(), 4);
}
#[test]
fn test_farm_rated_power() {
let farm = small_farm();
assert_eq!(farm.rated_power_kw(), 4.0 * 2000.0);
}
#[test]
fn test_farm_power_below_cutin_is_zero() {
let farm = small_farm();
let state = farm.compute(2.0, 270.0, 0.0, 15.0);
assert_eq!(state.total_power_kw, 0.0);
}
#[test]
fn test_wake_losses_positive() {
let farm = small_farm();
let state = farm.compute(10.0, 270.0, 0.0, 15.0);
assert!(state.total_power_kw > 0.0);
assert!(
state.wake_loss_fraction >= 0.0,
"loss={:.4}",
state.wake_loss_fraction
);
}
#[test]
fn test_capacity_factor_range() {
let farm = small_farm();
let state = farm.compute(10.0, 270.0, 0.0, 15.0);
assert!(
state.capacity_factor >= 0.0 && state.capacity_factor <= 1.0,
"CF={:.3}",
state.capacity_factor
);
}
}