19using namespace helios;
22 context = context_ptr;
30 context = context_ptr;
32 latitude = latitude_deg;
33 longitude = longitude_deg;
35 if (latitude_deg < -90 || latitude_deg > 90) {
36 std::cerr <<
"WARNING (SolarPosition): Latitude must be between -90 and +90 deg (a latitude of " << latitude <<
" was given). Default latitude is being used." << std::endl;
39 latitude = latitude_deg;
42 if (longitude < -180 || longitude > 180) {
43 std::cerr <<
"WARNING (SolarPosition): Longitude must be between -180 and +180 deg (a longitude of " << longitude <<
" was given). Default longitude is being used." << std::endl;
46 longitude = longitude_deg;
52 int solstice_day, LSTM;
53 float Gamma, delta, time_dec, B, EoT, TC, LST, h, theta, phi, rad;
63 delta = 0.006918f - 0.399912f * cos(Gamma) + 0.070257f * sin(Gamma) - 0.006758f * cos(2.f * Gamma) + 0.000907f * sin(2.f * Gamma) - 0.002697f * cos(3.f * Gamma) + 0.00148f * sin(3.f * Gamma);
66 EoT = 229.18f * (0.000075f + 0.001868f * cos(Gamma) - 0.032077f * sin(Gamma) - 0.014615f * cos(2.f * Gamma) - 0.04089f * sin(2.f * Gamma));
72 TC = 4.f * (LSTM - longitude) + EoT;
73 LST = time_dec + TC / 60.f;
75 h = (LST - 12.f) * 15.f * rad;
78 theta =
asin_safe(sin(latitude * rad) * sin(delta) + cos(latitude * rad) * cos(delta) * cos(h));
80 assert(theta > -0.5f *
M_PI && theta < 0.5f *
M_PI);
83 phi =
acos_safe((sin(delta) - sin(theta) * sin(latitude * rad)) / (cos(theta) * cos(latitude * rad)));
86 phi = 2.f *
M_PI - phi;
89 assert(phi > 0 && phi < 2.f *
M_PI);
99 for (
uint h = 1; h <= 23 && !found; h++) {
100 for (
uint m = 1; m <= 59 && !found; m++) {
117 for (
int h = 23; h >= 1 && !found; h--) {
118 for (
int m = 59; m >= 1 && !found; m--) {
132 if (issolarpositionoverridden) {
142 if (issolarpositionoverridden) {
143 zenith = sun_direction.
zenith;
152 if (issolarpositionoverridden) {
153 azimuth = sun_direction.
azimuth;
162 if (issolarpositionoverridden) {
163 sundirection = sun_direction;
165 sundirection = calculateSunDirection(context->
getTime(), context->
getDate());
172 if (issolarpositionoverridden) {
173 sundirection = sun_direction;
175 sundirection = calculateSunDirection(context->
getTime(), context->
getDate());
181 issolarpositionoverridden =
true;
182 sun_direction = sundirection;
186 float Eb_PAR, Eb_NIR, fdiff;
187 GueymardSolarModel(pressure_Pa, temperature_K, humidity_rel, turbidity, Eb_PAR, Eb_NIR, fdiff);
188 float Eb = Eb_PAR + Eb_NIR;
189 if (!cloudcalibrationlabel.empty()) {
190 applyCloudCalibration(Eb, fdiff);
196 float Eb_PAR, Eb_NIR, fdiff;
197 GueymardSolarModel(pressure_Pa, temperature_K, humidity_rel, turbidity, Eb_PAR, Eb_NIR, fdiff);
198 if (!cloudcalibrationlabel.empty()) {
199 applyCloudCalibration(Eb_PAR, fdiff);
205 float Eb_PAR, Eb_NIR, fdiff;
206 GueymardSolarModel(pressure_Pa, temperature_K, humidity_rel, turbidity, Eb_PAR, Eb_NIR, fdiff);
207 if (!cloudcalibrationlabel.empty()) {
208 applyCloudCalibration(Eb_NIR, fdiff);
214 float Eb_PAR, Eb_NIR, fdiff;
215 GueymardSolarModel(pressure_Pa, temperature_K, humidity_rel, turbidity, Eb_PAR, Eb_NIR, fdiff);
216 if (!cloudcalibrationlabel.empty()) {
217 applyCloudCalibration(Eb_PAR, fdiff);
222void SolarPosition::GueymardSolarModel(
float pressure,
float temperature,
float humidity,
float turbidity,
float &Eb_PAR,
float &Eb_NIR,
float &fdiff)
const {
224 float beta = turbidity;
230 if (theta <= 0.f || theta > 0.5 *
M_PI) {
237 float m = pow(cos(theta) + 0.15 * pow(93.885 - theta * 180 /
M_PI, -1.25), -1);
239 float E0_PAR = 635.4;
240 float E0_NIR = 709.7;
242 vec2 alpha(1.3, 1.3);
246 float mR = 1.f / (cos(theta) + 0.48353 * pow(theta * 180 /
M_PI, 0.095846) / pow(96.741 - theta * 180 /
M_PI, 1.754));
248 float mR_p = mR * pressure / 101325;
250 float TR_PAR = (1.f + 1.8169 * mR_p - 0.033454 * mR_p * mR_p) / (1.f + 2.063 * mR_p + 0.31978 * mR_p * mR_p);
252 float TR_NIR = (1.f - 0.010394 * mR_p) / (1.f - 0.00011042 * mR_p * mR_p);
255 mR = 1.f / (cos(theta) + 0.48353 * pow(theta * 180 /
M_PI, 0.095846) / pow(96.741 - theta * 180 /
M_PI, 1.754));
257 mR_p = mR * pressure / 101325;
259 float Tg_PAR = (1.f + 0.95885 * mR_p + 0.012871 * mR_p * mR_p) / (1.f + 0.96321 * mR_p + 0.015455 * mR_p * mR_p);
261 float Tg_NIR = (1.f + 0.27284 * mR_p - 0.00063699 * mR_p * mR_p) / (1.f + 0.30306 * mR_p);
263 float BR_PAR = 0.5f * (0.89013 - 0.0049558 * mR + 0.000045721 * mR * mR);
267 float Ba = 1.f - exp(-0.6931 - 1.8326 * cos(theta));
270 float uo = (235 + (150 + 40 * sin(0.9856 * (DOY - 30) *
M_PI / 180.f) + 20 * sin(3 * (longitude *
M_PI / 180.f + 20))) * pow(sin(1.28 * latitude *
M_PI / 180.f), 2)) * 0.001f;
274 float f1 = uo * (10.979 - 8.5421 * uo) / (1.f + 2.0115 * uo + 40.189 * uo * uo);
275 float f2 = uo * (-0.027589 - 0.005138 * uo) / (1.f - 2.4857 * uo + 13.942 * uo * uo);
276 float f3 = uo * (10.995 - 5.5001 * uo) / (1.f + 1.678 * uo + 42.406 * uo * uo);
277 float To_PAR = (1.f + f1 * mo + f2 * mo * mo) / (1.f + f3 * mo);
283 float mw = 1.f / (cos(theta) + 1.065 * pow(theta * 180 /
M_PI, 1.6132) / pow(111.55 - theta * 180 /
M_PI, 3.2629));
285 float g1 = (0.17499 + 41.654 * un - 2146.4 * un * un) / (1 + 22295 * un * un);
286 float g2 = un * (-1.2134 + 59.324 * un) / (1.f + 8847.8 * un * un);
287 float g3 = (0.17499 + 61.658 * un + 9196.4 * un * un) / (1.f + 74109 * un * un);
288 float Tn_PAR = fmin(1.f, (1.f + g1 * mw + g2 * mw * mw) / (1.f + g3 * mw));
289 float Tn_PAR_p = fmin(1.f, (1.f + g1 * 1.66 + g2 * 1.66 * 1.66) / (1.f + g3 * 1.66));
292 float Tn_NIR_p = 1.f;
295 float gamma = log(humidity) + 17.67 * (temperature - 273) / (243 + 25);
296 float tdp = 243 * gamma / (17.67 - gamma) * 9 / 5 + 32;
297 float w = exp((0.1133 - log(4.0 + 1)) + 0.0393 * tdp);
299 mw = 1.f / (cos(theta) + 1.1212 * pow(theta * 180 /
M_PI, 0.6379) / pow(93.781 - theta * 180 /
M_PI, 1.9203));
301 float h1 = w * (0.065445 + 0.00029901 * w) / (1.f + 1.2728 * w);
302 float h2 = w * (0.065687 + 0.0013218 * w) / (1.f + 1.2008 * w);
303 float Tw_PAR = (1.f + h1 * mw) / (1.f + h2 * mw);
304 float Tw_PAR_p = (1.f + h1 * 1.66) / (1.f + h2 * 1.66);
306 float c1 = w * (19.566 - 1.6506 * w + 1.0672 * w * w) / (1.f + 5.4248 * w + 1.6005 * w * w);
307 float c2 = w * (0.50158 - 0.14732 * w + 0.047584 * w * w) / (1.f + 1.1811 * w + 1.0699 * w * w);
308 float c3 = w * (21.286 - 0.39232 * w + 1.2692 * w * w) / (1.f + 4.8318 * w + 1.412 * w * w);
309 float c4 = w * (0.70992 - 0.23155 * w + 0.096541 * w * w) / (1.f + 0.44907 * w + 0.75425 * w * w);
310 float Tw_NIR = (1.f + c1 * mw + c2 * mw * mw) / (1.f + c3 * mw + c4 * mw * mw);
311 float Tw_NIR_p = (1.f + c1 * 1.66 + c2 * 1.66 * 1.66) / (1.f + c3 * 1.66 + c4 * 1.66 * 1.66);
314 float ma = 1.f / (cos(theta) + 0.16851 * pow(theta * 180 /
M_PI, 0.18198) / pow(95.318 - theta * 180 /
M_PI, 1.9542));
315 float ua = log(1.f + ma * beta);
317 float d0 = 0.57664 - 0.024743 * alpha.x;
318 float d1 = (0.093942 - 0.2269 * alpha.x + 0.12848 * alpha.x * alpha.x) / (1.f + 0.6418 * alpha.x);
319 float d2 = (-0.093819 + 0.36668 * alpha.x - 0.12775 * alpha.x * alpha.x) / (1.f - 0.11651 * alpha.x);
320 float d3 = alpha.x * (0.15232 - 0.08721 * alpha.x + 0.012664 * alpha.x * alpha.x) / (1.f - 0.90454 * alpha.x + 0.26167 * alpha.x * alpha.x);
321 float lambdae_PAR = (d0 + d1 * ua + d2 * ua * ua) / (1.f + d3 * ua * ua);
322 float Ta_PAR = exp(-ma * beta * pow(lambdae_PAR, -alpha.x));
324 float e0 = (1.183 - 0.022989 * alpha.y + 0.020829 * alpha.y * alpha.y) / (1.f + 0.11133 * alpha.y);
325 float e1 = (-0.50003 - 0.18329 * alpha.y + 0.23835 * alpha.y * alpha.y) / (1.f + 1.6756 * alpha.y);
326 float e2 = (-0.50001 + 1.1414 * alpha.y + 0.0083589 * alpha.y * alpha.y) / (1.f + 11.168 * alpha.y);
327 float e3 = (-0.70003 - 0.73587 * alpha.y + 0.51509 * alpha.y * alpha.y) / (1.f + 4.7665 * alpha.y);
328 float lambdae_NIR = (e0 + e1 * ua + e2 * ua * ua) / (1.f + e3 * ua);
329 float Ta_NIR = exp(-ma * beta * pow(lambdae_NIR, -alpha.y));
331 float omega_PAR = 1.0;
332 float omega_NIR = 1.0;
334 float Tas_PAR = exp(-ma * omega_PAR * beta * pow(lambdae_PAR, -alpha.x));
336 float Tas_NIR = exp(-ma * omega_NIR * beta * pow(lambdae_NIR, -alpha.y));
339 Eb_PAR = TR_PAR * Tg_PAR * To_PAR * Tn_PAR * Tw_PAR * Ta_PAR * E0_PAR;
340 Eb_NIR = TR_NIR * Tg_NIR * To_NIR * Tn_NIR * Tw_NIR * Ta_NIR * E0_NIR;
341 float Eb = Eb_PAR + Eb_NIR;
344 float Edp_PAR = To_PAR * Tg_PAR * Tn_PAR_p * Tw_PAR_p * (BR_PAR * (1.f - TR_PAR) * pow(Ta_PAR, 0.25) + Ba * TR_PAR * (1.f - pow(Tas_PAR, 0.25))) * E0_PAR;
345 float Edp_NIR = To_NIR * Tg_NIR * Tn_NIR_p * Tw_NIR_p * (BR_NIR * (1.f - TR_NIR) * pow(Ta_NIR, 0.25) + Ba * TR_NIR * (1.f - pow(Tas_NIR, 0.25))) * E0_NIR;
346 float Edp = Edp_PAR + Edp_NIR;
349 fdiff = Edp / (Eb + Edp);
351 assert(fdiff >= 0.f && fdiff <= 1.f);
358 float e0 = 611.f * exp(17.502f * (temperature_K - 273.f) / ((temperature_K - 273.f) + 240.9f)) * humidity_rel;
362 float xi = e0 / temperature_K * K;
363 float eps = 1.f - (1.f + xi) * exp(-sqrt(1.2f + 3.f * xi));
365 return eps * 5.67e-8 * pow(temperature_K, 4);
368float SolarPosition::turbidityResidualFunction(
float turbidity, std::vector<float> ¶meters,
const void *a_solarpositionmodel) {
370 auto *solarpositionmodel =
reinterpret_cast<const SolarPosition *
>(a_solarpositionmodel);
372 float pressure = parameters.at(0);
373 float temperature = parameters.at(1);
374 float humidity = parameters.at(2);
375 float flux_target = parameters.at(3);
377 float flux_model = solarpositionmodel->
getSolarFlux(pressure, temperature, humidity, turbidity) * cosf(solarpositionmodel->getSunZenith());
378 return flux_model - flux_target;
384 helios_runtime_error(
"ERROR (SolarPosition::calibrateTurbidityFromTimeseries): Timeseries variable " + timeseries_shortwave_flux_label_Wm2 +
" does not exist.");
389 float min_flux = 1e6;
391 int max_flux_index = 0;
392 for (
int t = 0; t < length; t++) {
394 if (flux < min_flux) {
397 if (flux > max_flux) {
403 if (max_flux < 750 || max_flux > 1200) {
404 helios_runtime_error(
"ERROR (SolarPosition::calibrateTurbidityFromTimeseries): The maximum flux for the timeseries data is not within the expected range. Either it is not solar flux data, or there are no clear sky days in the dataset");
405 }
else if (min_flux < 0) {
406 helios_runtime_error(
"ERROR (SolarPosition::calibrateTurbidityFromTimeseries): The minimum flux for the timeseries data is negative. Solar fluxes cannot be negative.");
409 std::vector<float> parameters{101325, 300, 0.5, max_flux};
411 SolarPosition solarposition_copy(UTC, latitude, longitude, context);
415 solarposition_copy.
setSunDirection(solarposition_copy.calculateSunDirection(time_max, date_max));
417 float turbidity =
fzero(turbidityResidualFunction, parameters, &solarposition_copy, 0.01);
419 return std::max(1e-4F, turbidity);
425 helios_runtime_error(
"ERROR (SolarPosition::enableCloudCalibration): Timeseries variable " + timeseries_shortwave_flux_label_Wm2 +
" does not exist.");
428 cloudcalibrationlabel = timeseries_shortwave_flux_label_Wm2;
432 cloudcalibrationlabel =
"";
435void SolarPosition::applyCloudCalibration(
float &R_calc_Wm2,
float &fdiff_calc)
const {
442 float fdiff = fmin(fmax(0, 1.f - (R_meas - R_calc_horiz) / (R_calc_horiz)), 1);
443 float R = R_calc_Wm2 * R_meas / R_calc_horiz;
445 if (fdiff > 0.001 && R_calc_horiz > 1.f) {