Skip to main content

pvlib/
temperature.rs

1/// Sandia Array Performance Model (SAPM) for cell and module temperature.
2/// 
3/// # Arguments
4/// * `poa_global` - Total incident irradiance on the module in W/m^2.
5/// * `temp_air` - Ambient air temperature in Celsius.
6/// * `wind_speed` - Wind speed in m/s at standard 10m height.
7/// * `a` - SAPM parameter `a` (empirical).
8/// * `b` - SAPM parameter `b` (empirical).
9/// * `delta_t` - SAPM parameter `delta_t` (temperature difference between cell and module back surface).
10/// * `irrad_ref` - Reference irradiance, typically 1000.0 W/m^2.
11/// 
12/// # Returns
13/// A tuple of `(temp_cell, temp_module)` in Celsius.
14#[inline]
15pub fn sapm_cell_temperature(poa_global: f64, temp_air: f64, wind_speed: f64, a: f64, b: f64, delta_t: f64, irrad_ref: f64) -> (f64, f64) {
16    if poa_global <= 0.0 {
17        return (temp_air, temp_air);
18    }
19    
20    let temp_module = temp_air + poa_global * (a + b * wind_speed).exp();
21    let temp_cell = temp_module + (poa_global / irrad_ref) * delta_t;
22    
23    (temp_cell, temp_module)
24}
25
26/// PVsyst cell temperature model.
27/// 
28/// # Arguments
29/// * `poa_global` - Total incident irradiance on the module in W/m^2.
30/// * `temp_air` - Ambient air temperature in Celsius.
31/// * `wind_speed` - Wind speed in m/s.
32/// * `u_c` - Constant heat transfer component (W/(m^2 K)).
33/// * `u_v` - Convective heat transfer component (W/(m^3 s K)).
34/// * `module_efficiency` - Module efficiency as a decimal (e.g. 0.15 for 15%).
35/// * `alpha_absorption` - Absorption coefficient (e.g., 0.9).
36/// 
37/// # Returns
38/// Cell temperature in Celsius.
39#[inline]
40pub fn pvsyst_cell_temperature(poa_global: f64, temp_air: f64, wind_speed: f64, u_c: f64, u_v: f64, module_efficiency: f64, alpha_absorption: f64) -> f64 {
41    if poa_global <= 0.0 {
42        return temp_air;
43    }
44    let h_total = (u_c + u_v * wind_speed).max(0.01);
45    temp_air + (alpha_absorption * poa_global * (1.0 - module_efficiency)) / h_total
46}
47
48/// Faiman (2008) cell temperature model.
49/// `T_cell = T_ambient + POA / (U0 + U1 * wind_speed)`
50/// 
51/// # References
52/// Faiman, D., 2008, "Assessing the outdoor operating temperature of photovoltaic modules," 
53/// Progress in Photovoltaics 16(4), pp. 307-315.
54/// 
55/// # Arguments
56/// * `poa_global` - Total incident irradiance on the module in W/m^2.
57/// * `temp_air` - Ambient air temperature in Celsius.
58/// * `wind_speed` - Wind speed in m/s.
59/// * `u0` - Constant heat transfer coefficient (typically 25.0).
60/// * `u1` - Convective heat transfer coefficient (typically 6.84).
61#[inline]
62pub fn faiman(poa_global: f64, temp_air: f64, wind_speed: f64, u0: f64, u1: f64) -> f64 {
63    if poa_global <= 0.0 {
64        return temp_air;
65    }
66    let h_total = (u0 + u1 * wind_speed).max(0.01);
67    temp_air + poa_global / h_total
68}
69
70/// Fuentes (1987) module temperature model.
71/// 
72/// Accounts for thermal capacitance and inertia of PV modules.
73/// 
74/// # References
75/// Fuentes, M.K., 1987. "A simplified thermal model for flat-plate photovoltaic arrays".
76#[inline]
77pub fn fuentes(poa_global: f64, temp_air: f64, wind_speed: f64, inoct: f64) -> f64 {
78    // Simplified equilibrium approximation (actual is differential eq over time)
79    if poa_global <= 0.0 { return temp_air; }
80    let h = 1.2 + 0.8 * wind_speed; // rough convective cooling
81    temp_air + poa_global * (inoct - 20.0) / (800.0 * h)
82}
83
84/// Ross module temperature model.
85///
86/// # References
87/// Ross, R. G., 1980. "Flat-Plate Photovoltaic Array Design Optimization".
88#[inline]
89pub fn ross(poa_global: f64, temp_air: f64, inoct: f64) -> f64 {
90    if poa_global <= 0.0 {
91        return temp_air;
92    }
93    let k = (inoct - 20.0) / 800.0;
94    temp_air + k * poa_global
95}
96
97/// SAPM module back-surface temperature model.
98///
99/// T_module = poa_global * exp(a + b * wind_speed) + temp_air
100///
101/// # Arguments
102/// * `poa_global` - Total incident irradiance [W/m^2].
103/// * `temp_air` - Ambient dry bulb temperature [C].
104/// * `wind_speed` - Wind speed at 10m height [m/s].
105/// * `a` - SAPM parameter a (default -3.56 for glass/polymer open rack).
106/// * `b` - SAPM parameter b (default -0.0750 for glass/polymer open rack).
107///
108/// # Returns
109/// Module back-surface temperature in Celsius.
110///
111/// # References
112/// King, D. et al, 2004, "Sandia Photovoltaic Array Performance Model", SAND Report 3535.
113#[inline]
114pub fn sapm_module(poa_global: f64, temp_air: f64, wind_speed: f64, a: f64, b: f64) -> f64 {
115    poa_global * (a + b * wind_speed).exp() + temp_air
116}
117
118/// SAPM module temperature with default parameters (glass/polymer, open rack).
119#[inline]
120pub fn sapm_module_default(poa_global: f64, temp_air: f64, wind_speed: f64) -> f64 {
121    sapm_module(poa_global, temp_air, wind_speed, -3.56, -0.0750)
122}
123
124/// Calculate cell temperature from module back-surface temperature using SAPM.
125///
126/// T_cell = module_temperature + poa_global / irrad_ref * delta_t
127///
128/// # Arguments
129/// * `module_temperature` - Module back-surface temperature [C].
130/// * `poa_global` - Total incident irradiance [W/m^2].
131/// * `delta_t` - Temperature difference between cell and module back [C].
132/// * `irrad_ref` - Reference irradiance, default 1000 W/m^2.
133///
134/// # Returns
135/// Cell temperature in Celsius.
136///
137/// # References
138/// King, D. et al, 2004, "Sandia Photovoltaic Array Performance Model", SAND Report 3535.
139#[inline]
140pub fn sapm_cell_from_module(module_temperature: f64, poa_global: f64, delta_t: f64, irrad_ref: f64) -> f64 {
141    module_temperature + poa_global / irrad_ref * delta_t
142}
143
144/// SAPM cell from module with default parameters (delta_t=3, irrad_ref=1000).
145#[inline]
146pub fn sapm_cell_from_module_default(module_temperature: f64, poa_global: f64) -> f64 {
147    sapm_cell_from_module(module_temperature, poa_global, 3.0, 1000.0)
148}
149
150/// Adjustment to NOCT for mounting standoff distance.
151///
152/// Piecewise function matching SAM implementation.
153fn adj_for_mounting_standoff(x: f64) -> f64 {
154    if x <= 0.0 {
155        0.0
156    } else if x < 0.5 {
157        18.0
158    } else if x < 1.5 {
159        11.0
160    } else if x < 2.5 {
161        6.0
162    } else if x <= 3.5 {
163        2.0
164    } else {
165        0.0
166    }
167}
168
169/// NOCT cell temperature model from the System Advisor Model (SAM).
170///
171/// Note: This function assumes effective_irradiance == poa_global, i.e., no
172/// spectral or IAM losses are applied to the irradiance used for temperature
173/// calculation. This matches the common use case where effective irradiance
174/// is not separately tracked for the thermal model. In pvlib-python, an
175/// optional `effective_irradiance` parameter allows decoupling these; this
176/// implementation does not yet support that.
177///
178/// # Arguments
179/// * `poa_global` - Total incident irradiance [W/m^2].
180/// * `temp_air` - Ambient dry bulb temperature [C].
181/// * `wind_speed` - Wind speed [m/s].
182/// * `noct` - Nominal operating cell temperature [C].
183/// * `module_efficiency` - Module efficiency at reference conditions.
184/// * `transmittance_absorptance` - Combined tau*alpha coefficient (default 0.9).
185/// * `array_height` - Height above ground: 1 or 2 stories (default 1).
186/// * `mount_standoff` - Distance between module and mounting surface in inches (default 4.0).
187///
188/// # Returns
189/// Cell temperature in Celsius.
190///
191/// # References
192/// Gilman, P. et al, 2018, "SAM Photovoltaic Model Technical Reference Update", NREL/TP-6A20-67399.
193#[allow(clippy::too_many_arguments)]
194#[inline]
195pub fn noct_sam(
196    poa_global: f64,
197    temp_air: f64,
198    wind_speed: f64,
199    noct: f64,
200    module_efficiency: f64,
201    transmittance_absorptance: f64,
202    array_height: u32,
203    mount_standoff: f64,
204) -> f64 {
205    let wind_adj = match array_height {
206        1 => 0.51 * wind_speed,
207        2 => 0.61 * wind_speed,
208        _ => 0.51 * wind_speed, // default to height 1
209    };
210
211    let noct_adj = noct + adj_for_mounting_standoff(mount_standoff);
212    let tau_alpha = transmittance_absorptance;
213
214    let cell_temp_init = poa_global / 800.0 * (noct_adj - 20.0);
215    let heat_loss = 1.0 - module_efficiency / tau_alpha;
216    let wind_loss = 9.5 / (5.7 + 3.8 * wind_adj);
217
218    temp_air + cell_temp_init * heat_loss * wind_loss
219}
220
221/// NOCT SAM with default parameters (transmittance_absorptance=0.9, array_height=1, mount_standoff=4.0).
222#[inline]
223pub fn noct_sam_default(
224    poa_global: f64,
225    temp_air: f64,
226    wind_speed: f64,
227    noct: f64,
228    module_efficiency: f64,
229) -> f64 {
230    noct_sam(poa_global, temp_air, wind_speed, noct, module_efficiency, 0.9, 1, 4.0)
231}
232
233/// Generic linear cell temperature model.
234///
235/// T_cell = temp_air + poa_global * (absorptance - module_efficiency) / (u_c + u_v * wind_speed)
236///
237/// # Arguments
238/// * `poa_global` - Total incident irradiance [W/m^2].
239/// * `temp_air` - Ambient dry bulb temperature [C].
240/// * `wind_speed` - Wind speed at 10m height [m/s].
241/// * `u_c` - Combined heat transfer coefficient at zero wind [(W/m^2)/C].
242/// * `u_v` - Wind influence on heat transfer [(W/m^2)/C/(m/s)].
243/// * `module_efficiency` - Module electrical efficiency.
244/// * `absorptance` - Light absorptance of the module.
245///
246/// # Returns
247/// Cell temperature in Celsius.
248///
249/// # References
250/// Driesse, A. et al, 2022, "PV Module Operating Temperature Model Equivalence
251/// and Parameter Translation", IEEE PVSC.
252#[inline]
253pub fn generic_linear(
254    poa_global: f64,
255    temp_air: f64,
256    wind_speed: f64,
257    u_c: f64,
258    u_v: f64,
259    module_efficiency: f64,
260    absorptance: f64,
261) -> f64 {
262    let heat_input = poa_global * (absorptance - module_efficiency);
263    let total_loss_factor = u_c + u_v * wind_speed;
264    temp_air + heat_input / total_loss_factor
265}
266
267/// Generic linear model with default parameters (u_c=29.0, u_v=0.0, efficiency=0.15, absorptance=0.9).
268#[inline]
269pub fn generic_linear_default(poa_global: f64, temp_air: f64, wind_speed: f64) -> f64 {
270    generic_linear(poa_global, temp_air, wind_speed, 29.0, 0.0, 0.15, 0.9)
271}
272