// Compute apparent sunset on a titled surface facing the equator // Duffie and Beckman (1991) eqn 2.19.3b public static double GetApparentSunsetHourAngleEquator // (o) apparent sunset hour angle [radians] (double Slope // (i) slope of receiving surface [radians] , double Lat // (i) latitude [radians, N > 0] , double Decl // (i) declination [radians] ) { double IsNorth, Aux1, Aux2; IsNorth = (Lat > 0) ? 1 : -1; Aux1 = Astro.GetSunsetHourAngle(Lat, Decl); Aux2 = Astro.GetSunsetHourAngle(Lat - IsNorth * Slope, Decl); return(Math.Min(Aux1, Aux2)); }
// Compute apparent sunrise and sunset times on a tilted surface of any orientation // Duffie and Beckman (1991) eqn 2.20.5e to 2.20.5i public static void CalcApparentSunsetHourAngle (double Lat // (i) latitude [radians] , double Decl // (i) declination [radians] , double Slope // (i) slope of receiving surface [radians] , double Azimuth // (i) azimuth angle of receiving surface [radians] , out double AppSunrise // (o) apparent sunrise hour angle [radians] , out double AppSunset // (o) apparent sunset hour angle [radians] , out double Sunset // (o) true sunset hour angle [radians] ) { // Declarations double aux1, aux2, aux3, disc; double cosInc, SunZenith, SunAzimuth; double A, B, C; // Calculate true sunset and parameters A, B and C Sunset = Astro.GetSunsetHourAngle(Lat, Decl); A = Math.Cos(Slope) + Math.Tan(Lat) * Math.Cos(Azimuth) * Math.Sin(Slope); B = Math.Cos(Sunset) * Math.Cos(Slope) + Math.Tan(Decl) * Math.Sin(Slope) * Math.Cos(Azimuth); C = Math.Sin(Slope) * Math.Sin(Azimuth) / Math.Cos(Lat); // Pathological case: the sun does not rise if (Sunset == 0) { AppSunrise = AppSunset = 0; return; } // Normal case aux1 = A * A + C * C; disc = aux1 - B * B; // Check discriminant. If it is positive or zero, the sun rises (tangentially) // on the surface. In that case, calculate apparent sunrise and apparent // sunset if (disc >= 0) { aux2 = C * Math.Sqrt(disc) / aux1; aux3 = A * B / aux1; AppSunrise = Math.Min(Sunset, Math.Acos(aux3 + aux2)); AppSunset = Math.Min(Sunset, Math.Acos(aux3 - aux2)); if ((A > 0 && B > 0) || (A >= B)) { AppSunrise = -AppSunrise; } else { AppSunset = -AppSunset; } } // If discriminant is negative, the surface is either always or never // illuminated during the day. To find which case applies, compute the // cosine of the angle of incidence on the surface at noon. If it is less // than zero, then the surface is always in the dark. If it is greater // than zero, then the surface is always illuminated else { Astro.CalcSunPositionHourAngle(Decl, 0, Lat, out SunZenith, out SunAzimuth); cosInc = GetCosIncidenceAngle(SunZenith, SunAzimuth, Slope, Azimuth); if (cosInc < 0) { AppSunrise = AppSunset = 0; } else { AppSunrise = -Sunset; AppSunset = Sunset; } } }
// Calculation method // There are 6 ways to call the Calculate method; for each, only some of the parameters are required to calculate global, beam and diffuse. The inputs can be // 1. global horizontal // 2. global horizontal and diffuse horizontal // 3. global horizontal and direct horizontal // 4. global horizontal and direct normal // 5. diffuse horizontal and direct horizontal // 6. diffuse horizontal and direct normal public void Calculate ( double Zenith // zenith angle of sun [radians] , double _HGlo = double.NaN // global horizontal irradiance [W/m2] , double _HDif = double.NaN // diffuse horizontal irradiance [W/m2] , double _NDir = double.NaN // direct normal irradiance [W/m2] , double _HDir = double.NaN // direct horizontal irradiance [W/m2] , double NExtra = double.NaN // normal extraterrestrial irradiance [W/m2] ) { // Set all values to an invalid // Check that at least some of the radiation inputs are properly defined try { // Either HGlo has to be defined, or HDif and one of the two direct components if (double.IsNaN(_HGlo) && (double.IsNaN(_HDif) || (double.IsNaN(HDir) && double.IsNaN(NDir)))) { throw new CASSYSException("Splitter: insufficient number of inputs defined."); } // If global is defined, cannot have both diffuse and direct defined if (!double.IsNaN(_HGlo) && !double.IsNaN(_HDif) && ((!double.IsNaN(_HDir)) || !double.IsNaN(_NDir))) { throw new CASSYSException("Splitter: cannot specify both diffuse and direct when global is used."); } // Cannot have direct normal and direct horizontal defined if (!double.IsNaN(_HDir) && !double.IsNaN(_NDir)) { throw new CASSYSException("Splitter: cannot specify both direct normal and direct horizontal."); } // If global is defined but neither diffuse and direct are defined, // then additional inputs are required to calculate them if (!double.IsNaN(_HGlo) && !double.IsNaN(_HDif) && !double.IsNaN(_HDir) && !double.IsNaN(_NDir)) { if (NExtra == double.NaN) { throw new CASSYSException("Splitter: Extraterrestrial irradiance is required when only global horizontal is specified."); } } } catch (CASSYSException cs) { ErrorLogger.Log(cs, ErrLevel.FATAL); } // Calculate cos of zenith angle double cosZ = Math.Cos(Zenith); // First case: only global horizontal is defined // Calculate diffuse using the Hollands and Orgill correlation try { // If only HGlo is specified this case is used, first check the values provided in the program: if (!double.IsNaN(_HGlo) && double.IsNaN(_HDif) && double.IsNaN(_NDir) && double.IsNaN(_HDir)) { // Initialize value of HGlo HGlo = _HGlo; // If sun below horizon, direct is zero and diffuse is global // Changed this to sun below 87.5° as high zenith angles sometimes caused problems of // high direct on tilted surfaces if (Zenith > 87.5 * DTOR || HGlo <= 0) { HDif = HGlo; HDir = NDir = 0; } // Compute diffuse fraction else { double kt = Sun.GetClearnessIndex(HGlo, NExtra, Zenith); double kd = Sun.GetDiffuseFraction(kt); kd = Math.Min(kd, 1.0); kd = Math.Max(kd, 0.0); // Compute diffuse and direct on horizontal HDif = HGlo * kd; HDir = HGlo - HDif; NDir = HDir / cosZ; } // Limit beam normal to clear sky value // ASHRAE clear sky model (ASHRAE Handbook - Fundamentals, 2013, ch. 14) with tau_b = 0.245, taud = 2.611, a_b = 0.668 and a_d = 0.227 // (these values are from Flagstaff, AZ, for the month of June, and lead to one of the highest beam/extraterrestrial ratios worldwide) double AirMass = Astro.GetAirMass(Zenith); double NDir_cs = NExtra * Math.Exp(-0.245 * Math.Pow(AirMass, 0.668)); NDir = Math.Min(NDir, NDir_cs); HDir = NDir * cosZ; HDif = HGlo - HDir; } // Second case: global horizontal and diffuse horizontal are defined then this else if (_HGlo != double.NaN && _HDif != double.NaN) { // If sun below horizon, direct is zero and diffuse is global // Changed this to sun below 87.5° as high zenith angles sometimes caused problems of // high direct on tilted surfaces if (Zenith > 87.5 * DTOR || _HGlo <= 0) { HGlo = _HGlo; HDif = _HGlo; HDir = NDir = 0; } else { HGlo = _HGlo; HDif = Math.Min(_HGlo, _HDif); HDir = HGlo - HDif; NDir = HDir / cosZ; } } // Third case: global horizontal and direct horizontal are defined else if (_HGlo != double.NaN && _HDir != double.NaN) { HGlo = _HGlo; HDir = Math.Min(_HGlo, _HDir); HDif = HGlo - HDir; NDir = HDir / cosZ; } // Fourth case: global horizontal and direct normal are defined else if (_HGlo != double.NaN && _NDir != double.NaN) { HGlo = _HGlo; HDir = Math.Min(HGlo, NDir * cosZ); HDif = HGlo - HDir; NDir = HDir / cosZ; } // Fifth case: diffuse horizontal and direct horizontal are defined else if (_HDif != double.NaN && _HDir != double.NaN) { HDif = _HDif; HDir = _HDir; HGlo = HDif + HDir; NDir = HDir / cosZ; } // Sixth case: diffuse horizontal and direct normal are defined else if (_HDif != double.NaN && _NDir != double.NaN) { HDif = _HDif; NDir = _NDir; HDir = NDir * cosZ; HGlo = HDif + HDir; } // Other cases: should never get there else { throw new CASSYSException("Splitter: unexpected case encountered."); } } catch (CASSYSException ex) { ErrorLogger.Log(ex, ErrLevel.FATAL); } }
// Calculation for inverter output power, using efficiency curve public void Calculate ( int DayOfYear // Day of year (1-365) , double Hour // Hour of day, in decimal format (11.75 = 11:45 a.m.) ) { double itsSLatR = Utilities.ConvertDtoR(itsSLat); double itsSLongR = Utilities.ConvertDtoR(itsSLong); double itsMLongR = Utilities.ConvertDtoR(itsMLong); try { if (DayOfYear < 1 || DayOfYear > 365 || Hour < 0 || Hour > 24) { throw new CASSYSException("Sun.Calculate: Invalid time stamp for sun position calculation"); } } catch (CASSYSException cs) { ErrorLogger.Log(cs, ErrLevel.FATAL); } // Compute declination and normal extraterrestrial Irradiance if day has changed // Compute Sunrise and Sunset hour angles if (DayOfYear != itsCurrentDayOfYear) { itsCurrentDayOfYear = DayOfYear; itsCurrentDecl = Astro.GetDeclination(itsCurrentDayOfYear); itsCurrentNExtra = Astro.GetNormExtra(itsCurrentDayOfYear); // Variables used to hold the apparent/true sunset and sunrise hour angles double appSunRiseHA; // Hour angle for Sunrise [radians] double appSunsetHA; // Hour angle for Sunset [radians] double trueSunsetHA; // True Sunset Hour angle [radians] // Invoking the Tilt method to get the values Tilt.CalcApparentSunsetHourAngle(itsSLatR, itsCurrentDecl, itsSurfaceSlope, Azimuth, out appSunRiseHA, out appSunsetHA, out trueSunsetHA); // Assigning to the output values AppSunriseHour = Math.Abs(appSunRiseHA) * Util.HAtoR; AppSunsetHour = Util.NoonHour + appSunsetHA * Util.HAtoR; TrueSunSetHour = Util.NoonHour + trueSunsetHA * Util.HAtoR; TrueSunRiseHour = TrueSunSetHour - Astro.GetDayLength(itsSLatR, itsCurrentDecl); // If using local standard time then modify the sunrise and sunset to match the local time stamp. if (itsLSTFlag) { TrueSunSetHour -= Astro.GetATmsST(DayOfYear, itsSLongR, itsMLongR) / 60; // Going from solar to local time TrueSunRiseHour = TrueSunSetHour - Astro.GetDayLength(itsSLatR, itsCurrentDecl); } } // Compute hour angle double SolarTime = Hour; if (itsLSTFlag) { SolarTime += Astro.GetATmsST(DayOfYear, itsSLongR, itsMLongR) / 60; // Going from local to solar time } double HourAngle = Astro.GetHourAngle(SolarTime); // Compute azimuth and zenith angles Astro.CalcSunPositionHourAngle(itsCurrentDecl, HourAngle, itsSLatR, out Zenith, out Azimuth); // Compute normal extraterrestrial Irradiance NExtra = itsCurrentNExtra; // Compute air mass AirMass = Astro.GetAirMass(Zenith); }