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