// Returns the Version of the CSYX file to be simulated public static void CheckCSYXVersion() { CASSYSCSYXVersion = doc.SelectSingleNode("/Site/Version").InnerXml; // Check if version number is specified. if (CASSYSCSYXVersion == "") { ErrorLogger.Log("The file does not have a valid version number. Please check the site file.", ErrLevel.FATAL); } // CASSYS Version check, if the version does not match, the program should warn the user. if (String.Compare(EngineVersion, CASSYSCSYXVersion) < 0) { ErrorLogger.Log("You are using an older version of the CASSYS Engine. Please update to the latest version available at https://github.com/CanadianSolar/CASSYS", ErrLevel.FATAL); } // Display the CSYX Version Number to the User Console.WriteLine("CASSYS Site File Version: " + CASSYSCSYXVersion); }
// Config will assign parameter variables their values as obtained from the XML file public void Config() { // Getting the Tilt Algorithm for the Simulation if (ReadFarmSettings.GetInnerText("Site", "TransEnum", ErrLevel.WARNING) == "0") { itsTiltAlgorithm = TiltAlgorithm.HAY; } else if (ReadFarmSettings.GetInnerText("Site", "TransEnum", ErrLevel.WARNING) == "1") { itsTiltAlgorithm = TiltAlgorithm.PEREZ; } else { ErrorLogger.Log("Tilter: Invalid tilt algorithm chosen by User. CASSYS uses Hay as default.", ErrLevel.WARNING); itsTiltAlgorithm = TiltAlgorithm.HAY; } // Assign the albedo parameters from the .CSYX file ConfigAlbedo(); }
// Reads EPW Files and provides the values to simulation object public void ParseEPWLine() { // Read Input file line and split line based on the delimiter, and assign variables as defined above string[] inputLineDelimited = InputFileReader.ReadFields(); DateTime dateAndTime; try { Year = int.Parse(inputLineDelimited[0]); // not used MonthOfYear = int.Parse(inputLineDelimited[1]); DayOfMonth = int.Parse(inputLineDelimited[2]); HourOfDay = double.Parse(inputLineDelimited[3]); minuteInterval = double.Parse(inputLineDelimited[4]); // EPW files are assumed to be in hourly intervals, so this is a check to ensure the assumption is true if (minuteInterval != 60 & minuteInterval != 0) { ErrorLogger.Log("EPW file is not in 60 minute intervals", ErrLevel.FATAL); } // Year is permanantly set to 2017 to prevent chronological error dateAndTime = new DateTime(2017, MonthOfYear, DayOfMonth).AddHours(HourOfDay); TAmbient = double.Parse(inputLineDelimited[6]); HGlo = double.Parse(inputLineDelimited[13]); HDiff = double.Parse(inputLineDelimited[15]); WindSpeed = double.Parse(inputLineDelimited[21]); TimeStamp = dateAndTime.ToString("yyyy-MM-dd HH:mm:ss"); Util.timeFormat = "yyyy-MM-dd HH:mm:ss"; ReadFarmSettings.UsePOA = false; ReadFarmSettings.UseDiffMeasured = true; ReadFarmSettings.UseWindSpeed = true; ReadFarmSettings.UseMeasuredTemp = false; } catch { ErrorLogger.Log("Error produced in loading EPW", ErrLevel.FATAL); inputRead = false; } }
// Reads .TM3 Files and provides the value to Simulation Object public void ParseTM3Line() { // Read Input file line and split line based on the delimiter, and assign variables as defined above string[] inputLineDelimited = InputFileReader.ReadFields(); DateTime simDateTime = new DateTime(); try { // Get the Inputs from the Input file as assigned by the .CSYX file // The Input order is set up in weatherRefPos and then the input line is broken into its constituents based on the user assignment // As of v. 1.3.1 date is handled first to correct potential issues with Feb 28 of leap year simDateTime = DateTime.Parse(inputLineDelimited[0]); simDateTime = new DateTime(1990, simDateTime.Month, simDateTime.Day).AddHours(Double.Parse(inputLineDelimited[1].Substring(0, inputLineDelimited[1].IndexOf(':')))); TimeStamp = simDateTime.ToString("yyyy-MM-dd HH:mm:ss"); Util.timeFormat = "yyyy-MM-dd HH:mm:ss"; TAmbient = double.Parse(inputLineDelimited[31]); HDiff = double.Parse(inputLineDelimited[10]); HGlo = double.Parse(inputLineDelimited[4]); WindSpeed = double.Parse(inputLineDelimited[46]); ReadFarmSettings.UsePOA = false; ReadFarmSettings.UseDiffMeasured = true; ReadFarmSettings.UseWindSpeed = true; ReadFarmSettings.UseMeasuredTemp = false; } catch (IndexOutOfRangeException) { ErrorLogger.Log("One of the Input columns was not defined correctly. Please check your Input file definition. Row was skipped.", ErrLevel.WARNING); inputRead = false; } catch (FormatException) { ErrorLogger.Log("Incorrect format for values in the Input String. Please check your Input file at the Input line specified above. Row was skipped.", ErrLevel.WARNING); inputRead = false; } catch (CASSYSException ex) { ErrorLogger.Log(ex, ErrLevel.WARNING); } }
// BEZIER INTERPOLATION // This is a 4-point method of Bezier Interpolation public static double Bezier(double[] xa, double[] ya, double x, int n) { //find interval x is in int pos = -1; for (int i = 0; i < n - 1; i++) { if (x >= xa[i] && x <= xa[i + 1]) { pos = i; } } if (pos == -1) { //can not extrapolate ErrorLogger.Log("The Bezier Interpolation cannot algorithm tried to perform an extrapolation. CASSYS has ended.", ErrLevel.WARNING); } //evaluate on the given interval Point pt; if (pos == 0) { pt = EvaluateBezier(xa, ya, 0, 0, 1, 2, -1, x); } else if (pos == n - 2) { pt = EvaluateBezier(xa, ya, pos - 1, pos, pos + 1, pos + 1, -1, x); } else { pt = EvaluateBezier(xa, ya, pos - 1, pos, pos + 1, pos + 2, -1, x); } //return Y value found return(pt.y); }
// Config will assign parameter variables their values as obtained from the .CSYX file public void Config() { try { // Gathering all the parameters from ASTM E2848 Element itsPmax = double.Parse(ReadFarmSettings.GetInnerText("ASTM", "SystemPmax", _Error: ErrLevel.FATAL)); itsA1 = double.Parse(ReadFarmSettings.GetInnerText("ASTM/Coeffs", "ASTM1", _Error: ErrLevel.FATAL)); itsA2 = double.Parse(ReadFarmSettings.GetInnerText("ASTM/Coeffs", "ASTM2", _Error: ErrLevel.FATAL)); itsA3 = double.Parse(ReadFarmSettings.GetInnerText("ASTM/Coeffs", "ASTM3", _Error: ErrLevel.FATAL)); itsA4 = double.Parse(ReadFarmSettings.GetInnerText("ASTM/Coeffs", "ASTM4", _Error: ErrLevel.FATAL)); // Looping through and assigning all EAF parameters from EAF Element string[] months = new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; for (int i = 0; i < 12; i++) { itsEAF[i] = double.Parse(ReadFarmSettings.GetInnerText("ASTM/EAF", months[i], _Error: ErrLevel.FATAL)); } } catch (Exception e) { ErrorLogger.Log("ASTM E2848 Config: " + e.Message, ErrLevel.FATAL); } }
// Finding and assigning the number of CellRows public static void AssignCellRowsNum() { if (string.Compare(SystemMode, "GridConnected") == 0) { // Getting the number of back cell rows for this file if (Convert.ToBoolean(ReadFarmSettings.GetInnerText("Bifacial", "UseBifacialModel", ErrLevel.FATAL))) { switch (ReadFarmSettings.GetAttribute("O&S", "ArrayType", ErrLevel.FATAL)) { case "Fixed Tilted Plane": numCellRows = Util.NUM_CELLS_PANEL * int.Parse(ReadFarmSettings.GetInnerText("O&S", "StrInWid", ErrLevel.WARNING, _default: "1")); break; case "Unlimited Rows": numCellRows = Util.NUM_CELLS_PANEL * int.Parse(ReadFarmSettings.GetInnerText("O&S", "StrInWid", ErrLevel.WARNING, _default: "1")); break; case "Single Axis Elevation Tracking (E-W)": numCellRows = Util.NUM_CELLS_PANEL * int.Parse(ReadFarmSettings.GetInnerText("O&S", "StrInWidSAET", ErrLevel.WARNING, _default: "1")); break; case "Single Axis Horizontal Tracking (N-S)": numCellRows = Util.NUM_CELLS_PANEL * int.Parse(ReadFarmSettings.GetInnerText("O&S", "StrInWidSAST", ErrLevel.WARNING, _default: "1")); break; default: ErrorLogger.Log("Bifacial is not supported for the selected orientation and shading.", ErrLevel.FATAL); break; } } else { numCellRows = Util.NUM_CELLS_PANEL; } } }
// Reads CSV Files and provides the values to Simulation Object public void ParseCSVLine() { // Read Input file line and split line based on the delimiter, and assign variables as defined above string[] inputLineDelimited = InputFileReader.ReadFields(); try { // Get the Inputs from the Input file as assigned by the .CSYX file // The Input order is setup in weatherRefPos and then the input line is broken into its constituents based on the user assignment TimeStamp = inputLineDelimited[ReadFarmSettings.ClimateRefPos[0] - 1]; if (ReadFarmSettings.UsePOA) { TGlo = double.Parse(inputLineDelimited[ReadFarmSettings.ClimateRefPos[2] - 1]); } else { if (ReadFarmSettings.UseDiffMeasured) { HDiff = double.Parse(inputLineDelimited[ReadFarmSettings.ClimateRefPos[6] - 1]); } HGlo = double.Parse(inputLineDelimited[ReadFarmSettings.ClimateRefPos[1] - 1]); } // If no system is defined proceed as normal to read and simulate file. if (ReadFarmSettings.SystemMode != "Radiation") { // Measured temperature is available, try and access the value from the Input file else assign not a number status if (ReadFarmSettings.UseMeasuredTemp) { TModMeasured = double.Parse(inputLineDelimited[ReadFarmSettings.ClimateRefPos[4] - 1]); } else { TModMeasured = double.NaN; } TAmbient = double.Parse(inputLineDelimited[ReadFarmSettings.ClimateRefPos[3] - 1]); if (ReadFarmSettings.UseWindSpeed) { WindSpeed = double.Parse(inputLineDelimited[ReadFarmSettings.ClimateRefPos[5] - 1]); } else { WindSpeed = double.NaN; } if (ReadFarmSettings.UseMeasuredAlbedo) { Albedo = double.Parse(inputLineDelimited[ReadFarmSettings.ClimateRefPos[7] - 1]); } else { Albedo = double.NaN; } } } catch (IndexOutOfRangeException) { ErrorLogger.Log("Incorrect number of fields in the above line. Row was skipped.", ErrLevel.WARNING); inputRead = false; } catch (FormatException) { ErrorLogger.Log("Incorrect format for values in the Input String. Please check your Input file at the Input line specified above. Row was skipped.", ErrLevel.WARNING); inputRead = false; } }
// Gets the day of the year based on a given date public static void TSBreak(String TimeStamp, out int dayOfYear, out double hour, out int year, out int month, out double nextTimeStampHour, out double baseTimeStampHour, SimMeteo simMeteoParser) { try { CurrentTimeStamp = DateTime.ParseExact(TimeStamp, Util.timeFormat, null); // Checks ensure the time series is always progressing forward. if (ErrorLogger.iterationCount != 1) { if (CurrentTimeStamp != cachedTimeStamp) { // Check if the time stamps are going back in time if (DateTime.Compare(CurrentTimeStamp, cachedTimeStamp) < 0) { ErrorLogger.Log("Time stamps in the Input File go backwards in time. Please check your input file. CASSYS has ended.", ErrLevel.FATAL); } } } else { // Get the next expected time stamp cachedTimeStamp = CurrentTimeStamp; } // Next and Base time stamps are used to check if the sun-rise and sun-set event occurs in between the time stamps under consideration DateTime nextTimeStamp = DateTime.ParseExact(TimeStamp, Util.timeFormat, null); DateTime baseTimeStamp = DateTime.ParseExact(TimeStamp, Util.timeFormat, null); switch (Util.AveragedAt) { case "Beginning": baseTimeStamp = CurrentTimeStamp; nextTimeStamp = baseTimeStamp.AddMinutes(Util.timeStep); CurrentTimeStamp = CurrentTimeStamp.AddMinutes(Util.timeStep / 2D); break; case "End": nextTimeStamp = CurrentTimeStamp; baseTimeStamp = CurrentTimeStamp.AddMinutes(-Util.timeStep); CurrentTimeStamp = CurrentTimeStamp.AddMinutes(-Util.timeStep / 2D); break; default: baseTimeStamp = CurrentTimeStamp.AddMinutes(-Util.timeStep / 2D); nextTimeStamp = CurrentTimeStamp.AddMinutes(Util.timeStep / 2D); CurrentTimeStamp = CurrentTimeStamp.AddMinutes(0); break; } dayOfYear = CurrentTimeStamp.DayOfYear; // Allowing for Leap Years - Assumes February 29 as Feb 28 and all other days as their day number during a normal year if ((CurrentTimeStamp.Month > 2) && (DateTime.IsLeapYear(CurrentTimeStamp.Year))) { if (dayOfYear > 59) { dayOfYear = CurrentTimeStamp.DayOfYear - 1; } } hour = CurrentTimeStamp.Hour + CurrentTimeStamp.Minute / 60D + CurrentTimeStamp.Second / 3600D; year = CurrentTimeStamp.Year; month = CurrentTimeStamp.Month; baseTimeStampHour = baseTimeStamp.Hour + baseTimeStamp.Minute / 60D + baseTimeStamp.Second / 3600D; nextTimeStampHour = nextTimeStamp.Hour + nextTimeStamp.Minute / 60D + nextTimeStamp.Second / 3600D; } catch (FormatException) { dayOfYear = 0; hour = 0; year = 0; month = 0; baseTimeStampHour = 0; nextTimeStampHour = 0; ErrorLogger.Log(TimeStamp + " was not recognized a valid DateTime. The date-time was expected in " + Util.timeFormat + " format. Please check Site definition file. Row was skipped", ErrLevel.WARNING); simMeteoParser.inputRead = false; } }
// Returns the attribute of the node, if the node and an attribute exist public static String GetAttribute(String Path, String AttributeName, ErrLevel _Error = ErrLevel.WARNING, String _VersionNum = "0.9", String _Adder = null, int _ArrayNum = 0) { try { if (String.Compare(EngineVersion, _VersionNum) >= 0) { switch (Path) { case "Site": Path = (String.Compare(CASSYSCSYXVersion, "1.5.2") < 0 ? "/Site" : "/SiteDef") + _Adder; break; case "Albedo": Path = (String.Compare(CASSYSCSYXVersion, "1.5.2") < 0 ? "/Site/Albedo" : "/Site/SiteDef/Albedo") + _Adder; break; case "O&S": Path = "/Site/Orientation_and_Shading" + _Adder; break; case "Bifacial": Path = "/Site/Bifacial" + _Adder; break; case "BifAlbedo": Path = "/Site/Bifacial/BifAlbedo" + _Adder; break; case "System": Path = "/Site/System" + _Adder; break; case "PV": Path = "/Site/System/" + "SubArray" + _ArrayNum + "/PVModule" + _Adder; break; case "Inverter": Path = "/Site/System/" + "SubArray" + _ArrayNum + "/Inverter" + _Adder; break; case "Losses": Path = (String.Compare(CASSYSCSYXVersion, "1.5.2") < 0 ? "/Site/System/Losses" : "/Site/Losses") + _Adder; break; case "SoilingLosses": Path = (String.Compare(CASSYSCSYXVersion, "1.5.2") < 0 ? "/Site/System/Losses/SoilingLosses" : "/Site/SoilingLosses") + _Adder; break; case "Spectral": Path = "/Site/Spectral" + _Adder; break; case "InputFile": Path = "/Site/InputFileStyle" + _Adder; break; case "OutputFile": Path = "/Site/OutputFileStyle" + _Adder; break; case "Iteration1": Path = "/Site/Iterations/Iteration1" + _Adder; break; } return(doc.SelectSingleNode(Path).Attributes[AttributeName].Value); } else { ErrorLogger.Log(AttributeName + " is not available in this version of CASSYS. Please update to the latest version available at https://github.com/CanadianSolar/CASSYS", ErrLevel.WARNING); return(null); } } catch (NullReferenceException) { if (_Error == ErrLevel.FATAL) { ErrorLogger.Log(AttributeName + " in " + Path + " is not defined. CASSYS requires this value to run.", ErrLevel.FATAL); return("N/A"); } else { ErrorLogger.Log(AttributeName + " in " + Path + " is not defined. CASSYS assigned 0 for this value.", ErrLevel.WARNING); return("0"); } } }
// Returns the value of the node, if the node exists public static String GetInnerText(String Path, String NodeName, ErrLevel _Error = ErrLevel.WARNING, String _VersionNum = "0.9", int _ArrayNum = 0, String _default = "0") { try { if (String.Compare(EngineVersion, _VersionNum) >= 0) { // Determine the Path of the .CSYX requested switch (Path) { case "Site": Path = "/Site/" + NodeName; break; case "SiteDef": Path = (String.Compare(CASSYSCSYXVersion, "1.5.2") < 0 ? "/Site/" : "/Site/SiteDef/") + NodeName; break; case "ASTM": Path = "/Site/ASTMRegress/" + NodeName; break; case "ASTM/Coeffs": Path = "/Site/ASTMRegress/ASTMCoeffs/" + NodeName; break; case "ASTM/EAF": Path = "/Site/ASTMRegress/EAF/" + NodeName; break; case "Albedo": Path = (String.Compare(CASSYSCSYXVersion, "1.5.2") < 0 ? "/Site/Albedo/" : "/Site/SiteDef/Albedo/") + NodeName; break; case "O&S": Path = "/Site/Orientation_and_Shading/" + NodeName; break; case "Bifacial": Path = "/Site/Bifacial/" + NodeName; break; case "BifAlbedo": Path = "/Site/Bifacial/BifAlbedo/" + NodeName; break; case "System": Path = "/Site/System/" + NodeName; break; case "PV": Path = "/Site/System/" + "SubArray" + _ArrayNum + "/PVModule/" + NodeName; break; case "Inverter": Path = "/Site/System/" + "SubArray" + _ArrayNum + "/Inverter/" + NodeName; break; case "Transformer": Path = (String.Compare(CASSYSCSYXVersion, "1.5.2") < 0 ? "/Site/System/Transformer/" : "/Site/Transformer/") + NodeName; break; case "Losses": Path = (String.Compare(CASSYSCSYXVersion, "1.5.2") < 0 ? "/Site/System/Losses/" : "/Site/Losses/") + NodeName; break; case "SoilingLosses": Path = (String.Compare(CASSYSCSYXVersion, "1.5.2") < 0 ? "/Site/System/Losses/SoilingLosses/" : "/Site/SoilingLosses/") + NodeName; break; case "Spectral": Path = "/Site/Spectral/" + NodeName; break; case "InputFile": Path = "/Site/InputFileStyle/" + NodeName; break; case "OutputFile": Path = "/Site/OutputFileStyle/" + NodeName; break; case "Iterations": Path = "/Site/Iterations/" + NodeName; break; } // Check if the .CSYX Blank, if it is, return the default value if (doc.SelectSingleNode(Path).InnerText == "") { if (_Error == ErrLevel.FATAL) { ErrorLogger.Log(NodeName + " is not defined. CASSYS requires this value to run.", ErrLevel.FATAL); return("N/A"); } else if (_Error == ErrLevel.WARNING) { ErrorLogger.Log("Warning: " + NodeName + " is not defined for this file. CASSYS assigned " + _default + " for this value.", ErrLevel.WARNING); return(_default); } else { return(_default); } } else { return(doc.SelectSingleNode(Path).InnerText); } } else { ErrorLogger.Log(NodeName + " is not supported in this version of CASSYS. Please update your CASSYS Site file using the latest version available at https://github.com/CanadianSolar/CASSYS", ErrLevel.WARNING); return(null); } } catch (NullReferenceException) { if (_Error == ErrLevel.WARNING || _Error == ErrLevel.INTERNAL) { return(_default); } else { ErrorLogger.Log(NodeName + " is not defined. CASSYS requires this value to run.", ErrLevel.FATAL); return("N/A"); } } }
// Divides the ground between two PV rows into n segments and determines direct beam shading (0 = not shaded, 1 = shaded) for each segment void CalcGroundShading ( double SunZenith // The zenith position of the sun with 0 being normal to the earth [radians] , double SunAzimuth // The azimuth position of the sun relative to 0 being true south. Positive if west, negative if east [radians] ) { // When sun is below horizon, set everything to shaded if (SunZenith > (Math.PI / 2)) { for (int i = 0; i < numGroundSegs; i++) { frontGroundSH[i] = 1; rearGroundSH[i] = 1; } } else { double h = Math.Sin(itsPanelTilt); // Vertical height of sloped PV panel [panel slope lengths] double b = Math.Cos(itsPanelTilt); // Horizontal distance from front of panel to back of panel [panel slope lengths] double FrontPA = Tilt.GetProfileAngle(SunZenith, SunAzimuth, itsPanelAzimuth); double Lh = h / Math.Tan(FrontPA); // Base of triangle formed by beam of sun and height of module top from bottom double Lc = itsClearance / Math.Tan(FrontPA); // Base of triangle formed by beam of sun and height of module bottom from ground double Lhc = (h + itsClearance) / Math.Tan(FrontPA); // Base of triangle formed by beam of sun and height of module top from ground double s1Start = 0; // Shading start position for first potential shading segment double s1End = 0; // Shading end position for first potential shading segment double s2Start = 0; // Shading start position for second potential shading segment double s2End = 0; // Shading end position for second potential shading segment double SStart = 0; // Shading start position for placeholder segment double SEnd = 0; // Shading start position for placeholder segment // Divide the row-to-row spacing into n intervals for calculating ground shade factors double delta = itsPitch / numGroundSegs; // Initialize horizontal dimension x to provide midpoint intervals double x = 0; if (itsRowType == RowType.SINGLE) { // Calculate front ground shading // Front side of PV module completely sunny, ground partially shaded if (Lh > 0.0) { s1Start = Lc; s1End = Lhc + b; } // Front side of PV module completely shaded, ground completely shaded else if (Lh < -(itsPitch + b)) { s1Start = -itsPitch; s1End = itsPitch; } // Shadow to front of row - either front or back might be shaded, depending on tilt and other factors else { // Sun hits front of module. Shadow cast by bottom of module extends further forward than shadow cast by top if (Lc < Lhc + b) { s1Start = Lc; s1End = Lhc + b; } // Sun hits back of module. Shadow cast by top of module extends further forward than shadow cast by bottom else { s1Start = Lhc + b; s1End = Lc; } } // Determine whether shaded or sunny for each n ground segments // TODO: improve accuracy (especially for n < 100) by setting 1 only if > 50% of segment is shaded for (int i = 0; i < numGroundSegs; i++) { // Offset x coordinate by -itsPitch because row ahead is being measured x = (i + 0.5) * delta - itsPitch; if (x >= s1Start && x < s1End) { // x within a shaded interval, so set to 1 to indicate shaded frontGroundSH[i] = 1; } else { // x not within a shaded interval, so set to 0 to indicate sunny frontGroundSH[i] = 0; } } // Calculate rear ground shading // Back side of PV module completely shaded, ground completely shaded if (Lh > itsPitch - b) { s1Start = 0.0; s1End = itsPitch; } // Shadow to front of row - either front or back might be shaded, depending on tilt and other factors else { // Sun hits front of module. Shadow cast by bottom of module extends further forward than shadow cast by top if (Lc < Lhc + b) { s1Start = Lc; s1End = Lhc + b; } // Sun hits back of module. Shadow cast by top of module extends further forward than shadow cast by bottom else { s1Start = Lhc + b; s1End = Lc; } } // Determine whether shaded or sunny for each n ground segments // TODO: improve accuracy (especially for n < 100) by setting 1 only if > 50% of segment is shaded for (int i = 0; i < numGroundSegs; i++) { x = (i + 0.5) * delta; if (x >= s1Start && x < s1End) { // x within a shaded interval, so set to 1 to indicate shaded rearGroundSH[i] = 1; } else { // x not within a shaded interval, so set to 0 to indicate sunny rearGroundSH[i] = 0; } } } else { // Calculate interior ground shading // Front side of PV module partially shaded, back completely shaded, ground completely shaded if (Lh > itsPitch - b) { s1Start = 0.0; s1End = itsPitch; } // Front side of PV module completely shaded, back partially shaded, ground completely shaded else if (Lh < -(itsPitch + b)) { s1Start = 0.0; s1End = itsPitch; } // Assume ground is partially shaded else { // Shadow to back of row - module front unshaded, back shaded if (Lhc >= 0.0) { SStart = Lc; SEnd = Lhc + b; // Put shadow in correct row-to-row space if needed while (SStart > itsPitch) { SStart -= itsPitch; SEnd -= itsPitch; } s1Start = SStart; s1End = SEnd; // Need to use two shade areas. Transpose the area that extends beyond itsPitch to the front of the row-to-row space if (s1End > itsPitch) { s1End = itsPitch; s2Start = 0.0; s2End = SEnd - itsPitch; if (s2End - s1Start > 0.000001) { ErrorLogger.Log("Unexpected shading coordinates encountered.", ErrLevel.FATAL); } } } // Shadow to front of row - either front or back might be shaded, depending on tilt and other factors else { // Sun hits front of module. Shadow cast by bottom of module extends further forward than shadow cast by top if (Lc < Lhc + b) { SStart = Lc; SEnd = Lhc + b; } // Sun hits back of module. Shadow cast by top of module extends further forward than shadow cast by bottom else { SStart = Lhc + b; SEnd = Lc; } // Put shadow in correct row-to-row space if needed while (SStart < 0.0) { SStart += itsPitch; SEnd += itsPitch; } s1Start = SStart; s1End = SEnd; // Need to use two shade areas. Transpose the area that extends beyond itsPitch to the front of the row-to-row space if (s1End > itsPitch) { s1End = itsPitch; s2Start = 0.0; s2End = SEnd - itsPitch; if (s2End - s1Start > 0.000001) { ErrorLogger.Log("Unexpected shading coordinates encountered.", ErrLevel.FATAL); } } } } // Determine whether shaded or sunny for each n ground segments // TODO: improve accuracy (especially for n < 100) by setting 1 only if > 50% of segment is shaded for (int i = 0; i < numGroundSegs; i++) { x = (i + 0.5) * delta; // Assume homogeneity to the front and rear of interior rows if ((x >= s1Start && x < s1End) || (x >= s2Start && x < s2End)) { // x within a shaded interval, so set to 1 to indicate shaded frontGroundSH[i] = 1; rearGroundSH[i] = 1; } else { // x not within a shaded interval, so set to 0 to indicate sunny frontGroundSH[i] = 0; rearGroundSH[i] = 0; } } } } }
public void Calculate ( SimMeteo SimMet // Meteological data from inputfile ) { // Calculating Sun position // Calculate the Solar Azimuth, and Zenith angles [radians] SimSun.itsSurfaceSlope = SimTracker.SurfSlope; SimSun.Calculate(SimMet.DayOfYear, SimMet.HourOfDay); HourOfDay = SimMet.HourOfDay; // The time stamp must be adjusted for sunset and sunrise hours such that the position of the sun is only calculated // for the middle of the interval where the sun is above the horizon. if ((SimMet.TimeStepEnd > SimSun.TrueSunSetHour) && (SimMet.TimeStepBeg < SimSun.TrueSunSetHour)) { HourOfDay = SimMet.TimeStepBeg + (SimSun.TrueSunSetHour - SimMet.TimeStepBeg) / 2; } else if ((SimMet.TimeStepBeg < SimSun.TrueSunRiseHour) && (SimMet.TimeStepEnd > SimSun.TrueSunRiseHour)) { HourOfDay = SimSun.TrueSunRiseHour + (SimMet.TimeStepEnd - SimSun.TrueSunRiseHour) / 2; } // Based on the definition of Input file, use Tilted irradiance or transpose the horizontal irradiance if (ReadFarmSettings.UsePOA == true) { // Check if the meter tilt and surface tilt are equal, if not detranspose the pyranometer if (string.Compare(ReadFarmSettings.CASSYSCSYXVersion, "0.9.2") >= 0) { // Checking if the Meter and Panel Tilt are different: if ((pyranoTilter.itsSurfaceAzimuth != SimTracker.SurfAzimuth) || (pyranoTilter.itsSurfaceSlope != SimTracker.SurfSlope)) { if (SimMet.TGlo < 0) { SimMet.TGlo = 0; if (negativeIrradFlag == false) { ErrorLogger.Log("Global Plane of Array Irradiance contains negative values. CASSYS will set the value to 0.", ErrLevel.WARNING); negativeIrradFlag = true; } } PyranoDetranspose(SimMet); } else { if (SimMet.TGlo < 0) { SimMet.TGlo = 0; if (negativeIrradFlag == false) { ErrorLogger.Log("Global Plane of Array Irradiance contains negative values. CASSYS will set the value to 0.", ErrLevel.WARNING); negativeIrradFlag = true; } } Detranspose(SimMet); } } else { if (SimMet.TGlo < 0) { SimMet.TGlo = 0; if (negativeIrradFlag == false) { ErrorLogger.Log("Global Plane of Array Irradiance contains negative values. CASSYS will the value to 0.", ErrLevel.WARNING); negativeIrradFlag = true; } } Detranspose(SimMet); } } else { if (SimMet.HGlo < 0) { SimMet.HGlo = 0; if (negativeIrradFlag == false) { ErrorLogger.Log("Global Horizontal Irradiance is negative. CASSYS set the value to 0.", ErrLevel.WARNING); negativeIrradFlag = true; } } if (ReadFarmSettings.UseDiffMeasured == true) { if (SimMet.HDiff < 0) { if (negativeIrradFlag == false) { SimMet.HDiff = 0; ErrorLogger.Log("Horizontal Diffuse Irradiance is negative. CASSYS set the value to 0.", ErrLevel.WARNING); negativeIrradFlag = true; } } } else { SimMet.HDiff = double.NaN; } Transpose(SimMet); } // Calculate horizon shading effects SimHorizonShading.Calculate(SimSun.Zenith, SimSun.Azimuth, SimTracker.SurfSlope, SimTracker.SurfAzimuth, SimTilter.TDir, SimTilter.TDif, SimTilter.TRef, SimSplitter.HDir, SimSplitter.HDif, SimTracker.itsTrackMode); // Assigning outputs AssignOutputs(); }
static void variableParameters(string[] args) { ReadFarmSettings.outputMode = "var"; ReadFarmSettings.batchMode = true; // Assign input and output file path for simulation run. // File paths have already been assigned in case of no input arguments if (args.Length == 1) { SimInputFilePath = null; SimOutputFilePath = null; } else if (args.Length == 3) { SimInputFilePath = args[1]; SimOutputFilePath = args[2]; } // Gathering information on parameter to be changed paramPath = ReadFarmSettings.GetAttribute("Iteration1", "ParamPath", _Error: ErrLevel.FATAL); start = double.Parse(ReadFarmSettings.GetAttribute("Iteration1", "Start", _Error: ErrLevel.FATAL)); end = double.Parse(ReadFarmSettings.GetAttribute("Iteration1", "End", _Error: ErrLevel.FATAL)); interval = double.Parse(ReadFarmSettings.GetAttribute("Iteration1", "Interval", _Error: ErrLevel.FATAL)); // Number of simulations ran ReadFarmSettings.runNumber = 1; // Data table to hold output for various runs ReadFarmSettings.outputTable = new DataTable(); // Row used to hold parameter information ReadFarmSettings.outputTable.Rows.InsertAt(ReadFarmSettings.outputTable.NewRow(), 0); // Loop through values of variable parameter for (double value = start; value <= end; value = value + interval) { // iterationCount used to determine current row in data base ErrorLogger.iterationCount = 0; // Reset outputheader between runs ReadFarmSettings.OutputHeader = null; // Vary parameter in XML object SiteSettings.SelectSingleNode(paramPath).InnerText = Convert.ToString(value); // Creating column to store data from run // A single column stores the all data from the simulation run ReadFarmSettings.outputTable.Columns.Add(paramPath + "=" + value, typeof(String)); PVPlant.Simulate(SiteSettings, SimInputFilePath, SimOutputFilePath); ReadFarmSettings.runNumber++; } try { // Write data table output to csv file OutputFileWriter = new StreamWriter(ReadFarmSettings.SimOutputFile); WriteDataTable(ReadFarmSettings.outputTable, OutputFileWriter); OutputFileWriter.Dispose(); } catch (IOException ex) { ErrorLogger.Log("Error occured while creating to output file. Error: " + 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); }
/////////////////////////////////////////////////////////////////////////////// // Calculation of global irradiance on a tilted surface using the Hay and // Davies model (Hay and Davies, 1978). // // Conversion for direct irradiance is geometric, for diffuse // irradiance an empirical model is used, and for reflected the // isotropic assumption is employed. double GetTiltCompIrradHay // (o) global irradiance on tilted surface [W/m2] (out double TDir // (o) beam irradiance on tilted surface [W/m2] , out double TDif // (o) diffuse irradiance on tilted surface [W/m2] , out double TRef // (o) reflected irradiance on tilted surface [W/m2] , double HDir // (i) direct irradiance on horizontal surface [W/m2] , double HDif // (i) diffuse irradiance on horizontal surface [W/m2] , double NExtra // (i) normal extraterrestrial irradiance [W/m2] , double SunZenith // (i) zenith angle of sun [radians] , double SunAzimuth // (i) azimuth angle of sun [radians] , int MonthNum // The month of the year number [1->12] ) { // Declarations double cosInc; // The cosine of the incidence angle double cosZenith; // The cosine of the zenith angle double Rb; // Rb is the ratio of beam radiation on the tilted surface to that on a horizontal surface eqn 1.8.1 double AI; // Anisotropy Index eqn. 2.16.2 and 2.16.3 double HGlo = HDir + HDif; // Global on horizontal double TGlo; // The global radiation in the plane of the array // Initialize values TGlo = TDif = TDir = TRef = 0; // Check arguments // Allow itsSurfaceAzimuth < -Math.PI and itsSurfaceAzimuth > Math.PI for bifacial modelling if (NExtra < 0 || SunZenith < 0 || SunZenith > Math.PI || SunAzimuth < -Math.PI || SunAzimuth > Math.PI || itsSurfaceSlope < 0 || itsSurfaceSlope > Math.PI || itsMonthlyAlbedo[MonthNum] < 0 || itsMonthlyAlbedo[MonthNum] > 1) { ErrorLogger.Log("GetTiltCompIrradHay: out of range arguments.", ErrLevel.FATAL); } // Negative values: return zero if (HDif <= 0 && HDir <= 0) { return(0.0); } // Compute cosine of incidence angle and cosine of zenith angle // cos(Zenith) is bound by cos(89 degrees) to avoid large values // near sunrise and sunset. cosInc = Math.Cos(SunZenith) * Math.Cos(itsSurfaceSlope) + Math.Sin(SunZenith) * Math.Sin(itsSurfaceSlope) * Math.Cos(itsSurfaceAzimuth - SunAzimuth); cosZenith = Math.Max(Math.Cos(SunZenith), Math.Cos(89.0 * Util.DTOR)); // Compute tilted beam irradiance // Rb is the ratio of beam radiation on the tilted surface to that on // a horizontal surface. Duffie and Beckman (1991) eqn 1.8.1 // note: to avoid problems at low sun angles, HDir/cosZenith is limited // to 90% of solar constant Rb = 0; if (SunZenith < Math.PI / 2 && cosInc > 0) { Rb = cosInc / cosZenith; } TDir = Math.Max(Math.Min(HDir * Rb, 0.9 * Util.SOLAR_CONST * cosInc), 0); // Compute anisotropy index AI and diffuse radiation // Duffie and Beckman (1991) eqn. 2.16.2 and 2.16.3 AI = Math.Min(HDir / NExtra / cosZenith, 1.0); // Calculate diffuse irradiance // There is no special treatment for sun below horizon or sun behind panel, as Rb = 0 then TDif = HDif * (AI * Rb + (1 - AI) * (1 + Math.Cos(itsSurfaceSlope)) / 2); // Compute ground-reflected irradiance TRef = HGlo * itsMonthlyAlbedo[MonthNum] * (1 - Math.Cos(itsSurfaceSlope)) / 2; // Compute titled global irradiance TGlo = TDir + TDif + TRef; // Normal end of subroutine return(TGlo); }
// Calculation of global irradiance on a tilted surface using the Perez et al. // model (Perez et al, 1990). // Conversion for direct irradiance is geometric, for diffuse // irradiance an empirical model is used, and for reflected the // isotropic assumption is employed. double GetTiltCompIrradPerez // (o) global irradiance on tilted surface [W/m2] (out double TDir // (o) beam // on tilted surface [W/m2] , out double TDif // (o) diffuse irradiance on tilted surface [W/m2] , out double TRef // (o) reflected irradiance on tilted surface [W/m2] , double HDir // (i) direct irradiance on horizontal surface [W/m2] , double HDif // (i) diffuse irradiance on horizontal surface [W/m2] , double NExtra // (i) normal extraterrestrial irradiance [W/m2] , double SunZenith // (i) zenith angle of sun [radians] , double SunAzimuth // (i) azimuth angle of sun [radians] , double AirMass // (i) air mass [] , int MonthNum // (i) The month number 1 -> 12 used to allow monthly albedo ) { // Declarations int ibin; double delta, eps; double a, b, fone, ftwo; // Define constants // f = circumsolar and horizon brightening coefficients // epsbin: bins for sky's clearness // kappa: double[, ,] f = new double[2, 3, 8] { { { -0.008, 0.130, 0.330, 0.568, 0.873, 1.132, 1.060, 0.678 } , { 0.588, 0.683, 0.487, 0.187, -0.392, -1.237, -1.600, -0.327 } , { -0.062, -0.151, -0.221, -0.295, -0.362, -0.412, -0.359, -0.250 } } , { { -0.060, -0.019, 0.055, 0.109, 0.226, 0.288, 0.264, 0.156 } , { 0.072, 0.066, -0.064, -0.152, -0.462, -0.823, -1.127, -1.377 } , { -0.022, -0.029, -0.026, -0.014, 0.001, 0.056, 0.131, 0.251 } } }; double[] epsbin = new double[7] { 1.065, 1.23, 1.5, 1.95, 2.8, 4.5, 6.2 }; double kappa = 1.041; // auxiliary quantities double cosZenith; // cos of zenith angle double cosInc; // cos incidence angle on slope double HGlo; // global irradiance on horizontal double rd1; // rd1, rd2: auxiliary quantities equal to (1+cos(slope))/2 double rd2; // and (1-cos(slope))/2 // initialize values TGlo = TDif = TDir = TRef = 0; // Check arguments // Allow itsSurfaceAzimuth < -Math.PI and itsSurfaceAzimuth > Math.PI for bifacial modelling if (NExtra < 0 || SunZenith < 0 || SunZenith > Math.PI || SunAzimuth < -Math.PI || SunAzimuth > Math.PI || AirMass < 1 || itsSurfaceSlope < 0 || itsSurfaceSlope > Math.PI || itsMonthlyAlbedo[MonthNum] < 0 || itsMonthlyAlbedo[MonthNum] > 1) { ErrorLogger.Log("GetTiltCompIrradPerez: out of range arguments.", ErrLevel.FATAL); } // Compute cosine of incidence angle and cosine of zenith angle // cos(Zenith) is bound by cos(89 degrees) to avoid large values // near sunrise and sunset. cosZenith = Math.Max(Math.Cos(SunZenith), Math.Cos(89.0 * Util.DTOR)); cosInc = Math.Cos(SunZenith) * Math.Cos(itsSurfaceSlope) + Math.Sin(SunZenith) * Math.Sin(itsSurfaceSlope) * Math.Cos(itsSurfaceAzimuth - SunAzimuth); HGlo = HDif + HDir; rd1 = (1.0 + Math.Cos(itsSurfaceSlope)) / 2; rd2 = (1.0 - Math.Cos(itsSurfaceSlope)) / 2; // Negative values: return zero if (HDif <= 0 && HDir <= 0) { return(0.0); } // If sun below horizon, treat all irradiance as diffuse isotropic if (SunZenith >= Math.PI / 2) { TDif = HGlo * (1 + Math.Cos(itsSurfaceSlope)) / 2; } // Normal case else { // Compute delta, eps, and bin number // delta = parametrization of sky's brightness // eps = parametrization of sky's clearness // ibin: bin number // normally delta is in the range 0.08-0.48 (see Perez et al., 1990, fig. 5) // but if the input data is wrong the values could be much higher, which can // then cause problems in the calculation of tilted diffuse irradiance (TDif). // Therefore we limit delta to 1. delta = Math.Min(HDif * AirMass / NExtra, 1); eps = 1 + HDir / cosZenith / HGlo / (1 + kappa * Math.Pow(SunZenith, 3)); for (ibin = 0; ibin < 7 && eps > epsbin[ibin]; ibin++) { ; } // calculation of empirical coefficients a and b a = Math.Max(0.0, cosInc); b = Math.Max(Math.Cos(85.0 * Util.DTOR), cosZenith); // calculation of empirical coefficient fone and ftwo fone = Math.Max(0.0, f[0, 0, ibin] + delta * f[0, 1, ibin] + SunZenith * f[0, 2, ibin]); ftwo = f[1, 0, ibin] + delta * f[1, 1, ibin] + SunZenith * f[1, 2, ibin]; // calculation of diffuse irradiance on the sloping surface TDif = HDif * ((1 - fone) * rd1 + fone * a / b + ftwo * Math.Sin(itsSurfaceSlope)); } // calculation of direct irradiance on a sloping surface // this is just a trigonometric transformation // note: to avoid problems at low sun angles, HDir/cosZenith is limited // to 90% of solar constant TDir = Math.Max(Math.Min(HDir / cosZenith, 0.9 * Util.SOLAR_CONST) * cosInc, 0); // calculation of reflected irradiance onto the slope // from Ineichen. P.et al., Solar Energy, 41(4), 371-377, 1988 // a simple assumption of isotropic reflection is used TRef = (HDir + HDif) * itsMonthlyAlbedo[MonthNum] * rd2; // summation for global irradiance on slope TGlo = TDir + TDif + TRef; // end of subroutine return(TGlo); }
// 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); } }
double farmACMinVoltageLoss = 0; // Loss of power when voltage of the array is too small and forces the inverters to 'shut off' and when inverter is not operating at MPP [W] // Calculate method public void Calculate( RadiationProc RadProc, // Radiation related data SimMeteo SimMet // Meteological data from inputfile ) { // Reset Losses for (int i = 0; i < SimPVA.Length; i++) { SimInv[i].LossPMinThreshold = 0; SimInv[i].LossClipping = 0; SimInv[i].LossLowVoltage = 0; SimInv[i].LossHighVoltage = 0; } // Calculating solar panel shading SimShading.Calculate(RadProc.SimSun.Zenith, RadProc.SimSun.Azimuth, RadProc.SimHorizonShading.TDir, RadProc.SimHorizonShading.TDif, RadProc.SimHorizonShading.TRef, RadProc.SimTracker.SurfSlope, RadProc.SimTracker.SurfAzimuth); // Calculating spectral model effects SimSpectral.Calculate(SimMet.HGlo, RadProc.SimSun.NExtra, RadProc.SimSun.Zenith); try { // Calculate PV Array Output for inputs read in this loop for (int j = 0; j < ReadFarmSettings.SubArrayCount; j++) { // Adjust the IV Curve based on based on Temperature and Irradiance SimPVA[j].CalcIVCurveParameters(SimMet.TGlo, SimShading.ShadTDir, SimShading.ShadTDif, SimShading.ShadTRef, RadProc.SimTilter.IncidenceAngle, SimMet.TAmbient, SimMet.WindSpeed, SimMet.TModMeasured, SimMet.MonthOfYear, SimSpectral.clearnessCorrection); // Check Inverter status to determine if the Inverter is ON or OFF GetInverterStatus(j); // If inverter is off set appropriate variables to 0 and recalculate array in open circuit voltage if (!SimInv[j].isON) { SimInv[j].ACPwrOut = 0; SimInv[j].IOut = 0; SimPVA[j].CalcAtOpenCircuit(); SimPVA[j].Calculate(false, SimPVA[j].Voc); } //performing AC wiring calculations SimACWiring[j].Calculate(SimInv[j]); // Assigning the outputs to the dictionary ReadFarmSettings.Outputlist["SubArray_Current" + (j + 1).ToString()] = SimPVA[j].IOut; ReadFarmSettings.Outputlist["SubArray_Voltage" + (j + 1).ToString()] = SimPVA[j].VOut; ReadFarmSettings.Outputlist["SubArray_Power" + (j + 1).ToString()] = SimPVA[j].POut / 1000; ReadFarmSettings.Outputlist["SubArray_Current_Inv" + (j + 1).ToString()] = SimInv[j].IOut; ReadFarmSettings.Outputlist["SubArray_Voltage_Inv" + (j + 1).ToString()] = SimInv[j].itsOutputVoltage; ReadFarmSettings.Outputlist["SubArray_Power_Inv" + (j + 1).ToString()] = SimInv[j].ACPwrOut / 1000; } //Calculating total farm output and total ohmic loss farmACOutput = 0; farmACOhmicLoss = 0; for (int i = 0; i < SimInv.Length; i++) { farmACOutput += SimInv[i].ACPwrOut; farmACOhmicLoss += SimACWiring[i].ACWiringLoss; } SimTransformer.Calculate(farmACOutput - farmACOhmicLoss); // Calculating outputs that will be assigned for this interval // Shading each component of the Tilted radiaton // Using horizon affected tilted radiation ShadGloLoss = (RadProc.SimTilter.TGlo - SimShading.ShadTGlo) - RadProc.SimHorizonShading.LossGlo; ShadGloFactor = (RadProc.SimTilter.TGlo > 0 ? SimShading.ShadTGlo / RadProc.SimTilter.TGlo : 1); ShadBeamLoss = RadProc.SimHorizonShading.TDir - SimShading.ShadTDir; ShadDiffLoss = RadProc.SimTilter.TDif > 0 ? RadProc.SimHorizonShading.TDif - SimShading.ShadTDif : 0; ShadRefLoss = RadProc.SimTilter.TRef > 0 ? RadProc.SimHorizonShading.TRef - SimShading.ShadTRef : 0; //Calculating total farm level variables. Cleaning them so they are non-cumulative. farmDC = 0; farmDCCurrent = 0; farmDCMismatchLoss = 0; farmDCModuleQualityLoss = 0; farmDCOhmicLoss = 0; farmDCSoilingLoss = 0; farmDCTemp = 0; farmTotalModules = 0; farmPNomDC = 0; farmPNomAC = 0; farmACPMinThreshLoss = 0; farmACClippingPower = 0; farmACMaxVoltageLoss = 0; farmACMinVoltageLoss = 0; farmPnom = 0; farmTempLoss = 0; farmRadLoss = 0; for (int i = 0; i < SimPVA.Length; i++) { farmDC += SimPVA[i].POut; farmDCCurrent += SimPVA[i].IOut; farmDCMismatchLoss += Math.Max(0, SimPVA[i].MismatchLoss); farmDCModuleQualityLoss += SimPVA[i].ModuleQualityLoss; farmDCOhmicLoss += SimPVA[i].OhmicLosses; farmDCSoilingLoss += SimPVA[i].SoilingLoss; farmDCTemp += SimPVA[i].TModule * SimPVA[i].itsNumModules; farmTotalModules += SimPVA[i].itsNumModules; farmPNomDC += SimPVA[i].itsPNomDCArray; farmPNomAC += SimInv[i].itsPNomArrayAC; farmACPMinThreshLoss += SimInv[i].LossPMinThreshold; farmACClippingPower += SimInv[i].LossClipping; farmACMaxVoltageLoss += SimInv[i].LossHighVoltage; farmACMinVoltageLoss += SimInv[i].LossLowVoltage; farmPnom += (SimPVA[i].itsPNom * SimPVA[i].itsNumModules) * SimPVA[i].TGloEff / 1000; farmTempLoss += SimPVA[i].tempLoss; farmRadLoss += SimPVA[i].radLoss; } // Averages all PV Array temperature values farmDCTemp /= farmTotalModules; farmModuleTempAndAmbientTempDiff = farmDCTemp - SimMet.TAmbient; farmDCEfficiency = (RadProc.SimTilter.TGlo > 0 ? farmDC / (RadProc.SimTilter.TGlo * farmArea) : 0) * 100; farmPNomDC = Utilities.ConvertWtokW(farmPNomDC); farmPNomAC = Utilities.ConvertWtokW(farmPNomAC); farmOverAllEff = (RadProc.SimTilter.TGlo > 0 && SimTransformer.POut > 0 ? SimTransformer.POut / (RadProc.SimTilter.TGlo * farmArea) : 0) * 100; farmPR = RadProc.SimTilter.TGlo > 0 && farmPNomDC > 0 && SimTransformer.POut > 0 ? SimTransformer.POut / RadProc.SimTilter.TGlo / farmPNomDC : 0; farmSysIER = (SimTransformer.itsPNom - SimTransformer.POut) / (RadProc.SimTilter.TGlo * 1000); } catch (Exception ce) { ErrorLogger.Log(ce, ErrLevel.FATAL); } // Assigning Outputs for this class. AssignOutputs(); }
// Gathering the tracker mode, and relevant operational limits, and tracking axis characteristics. public void Config() { switch (ReadFarmSettings.GetAttribute("O&S", "ArrayType", ErrLevel.FATAL)) { case "Fixed Tilted Plane": itsTrackMode = TrackMode.NOAT; if (String.Compare(ReadFarmSettings.CASSYSCSYXVersion, "0.9.3") >= 0) { SurfSlope = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PlaneTiltFix", ErrLevel.FATAL)); SurfAzimuth = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "AzimuthFix", ErrLevel.FATAL)); } else { SurfSlope = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PlaneTilt", ErrLevel.FATAL)); SurfAzimuth = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "Azimuth", ErrLevel.FATAL)); } break; case "Fixed Tilted Plane Seasonal Adjustment": itsTrackMode = TrackMode.FTSA; SurfAzimuth = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "AzimuthSeasonal", ErrLevel.FATAL)); itsSummerMonth = DateTime.ParseExact(ReadFarmSettings.GetInnerText("O&S", "SummerMonth", _Error: ErrLevel.FATAL), "MMM", CultureInfo.CurrentCulture).Month; itsWinterMonth = DateTime.ParseExact(ReadFarmSettings.GetInnerText("O&S", "WinterMonth", _Error: ErrLevel.FATAL), "MMM", CultureInfo.CurrentCulture).Month; itsSummerDay = int.Parse(ReadFarmSettings.GetInnerText("O&S", "SummerDay", _Error: ErrLevel.FATAL)); itsWinterDay = int.Parse(ReadFarmSettings.GetInnerText("O&S", "WinterDay", _Error: ErrLevel.FATAL)); itsPlaneTiltSummer = Util.DTOR * double.Parse(ReadFarmSettings.GetInnerText("O&S", "PlaneTiltSummer", _Error: ErrLevel.FATAL)); itsPlaneTiltWinter = Util.DTOR * double.Parse(ReadFarmSettings.GetInnerText("O&S", "PlaneTiltWinter", _Error: ErrLevel.FATAL)); // Assume the simualtion will begin when the array is in the summer tilt SurfSlope = itsPlaneTiltSummer; break; case "Unlimited Rows": itsTrackMode = TrackMode.NOAT; // Defining all the parameters for the shading of a unlimited row array configuration SurfSlope = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PlaneTilt", ErrLevel.FATAL)); SurfAzimuth = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "Azimuth", ErrLevel.FATAL)); break; case "Single Axis Elevation Tracking (E-W)": // Tracker Parameters itsTrackMode = TrackMode.SAXT; itsTrackerAzimuth = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "AxisAzimuthSAET", ErrLevel.FATAL)); itsTrackerSlope = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "AxisTiltSAET", ErrLevel.FATAL)); // Operational Limits itsMinTilt = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "MinTiltSAET", ErrLevel.FATAL)); itsMaxTilt = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "MaxTiltSAET", ErrLevel.FATAL)); // Backtracking Options useBackTracking = Convert.ToBoolean(ReadFarmSettings.GetInnerText("O&S", "BacktrackOptSAET", ErrLevel.WARNING, _default: "false")); if (useBackTracking) { itsTrackerPitch = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PitchSAET", ErrLevel.FATAL)); itsTrackerBW = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "WActiveSAET", ErrLevel.FATAL)); } break; case "Single Axis Horizontal Tracking (N-S)": // Tracker Parameters itsTrackMode = TrackMode.SAXT; itsTrackerSlope = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "AxisTiltSAST", ErrLevel.FATAL)); itsTrackerAzimuth = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "AxisAzimuthSAST", ErrLevel.FATAL)); // Operational Limits itsMaxTilt = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "RotationMaxSAST", ErrLevel.FATAL)); // Backtracking Options useBackTracking = Convert.ToBoolean(ReadFarmSettings.GetInnerText("O&S", "BacktrackOptSAST", ErrLevel.WARNING, _default: "false")); if (useBackTracking) { itsTrackerPitch = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PitchSAST", ErrLevel.FATAL)); itsTrackerBW = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "WActiveSAST", ErrLevel.FATAL)); } break; case "Tilt and Roll Tracking": // Tracker Parameters itsTrackMode = TrackMode.SAXT; itsTrackerSlope = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "AxisTiltTART", ErrLevel.FATAL)); itsTrackerAzimuth = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "AxisAzimuthTART", ErrLevel.FATAL)); // Operational Limits itsMinRotationAngle = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "RotationMinTART", ErrLevel.FATAL)); itsMaxRotationAngle = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "RotationMaxTART", ErrLevel.FATAL)); break; case "Two Axis Tracking": itsTrackMode = TrackMode.TAXT; // Operational Limits itsMinTilt = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "MinTiltTAXT", ErrLevel.FATAL)); itsMaxTilt = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "MaxTiltTAXT", ErrLevel.FATAL)); itsAzimuthRef = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "AzimuthRefTAXT", ErrLevel.FATAL)); itsMinAzimuth = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "MinAzimuthTAXT", ErrLevel.FATAL)); itsMaxAzimuth = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "MaxAzimuthTAXT", ErrLevel.FATAL)); break; case "Azimuth (Vertical Axis) Tracking": itsTrackMode = TrackMode.AVAT; // Surface Parameters SurfSlope = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PlaneTiltAVAT", ErrLevel.FATAL)); // Operational Limits itsAzimuthRef = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "AzimuthRefAVAT", ErrLevel.FATAL)); itsMinAzimuth = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "MinAzimuthAVAT", ErrLevel.FATAL)); itsMaxAzimuth = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "MaxAzimuthAVAT", ErrLevel.FATAL)); break; default: ErrorLogger.Log("No orientation and shading was specified by the user.", ErrLevel.FATAL); break; } }
// Calculate the tracker slope, azimuth and incidence angle using public void Calculate(double SunZenith, double SunAzimuth, int Year, int DayOfYear) { switch (itsTrackMode) { case TrackMode.SAXT: // Surface stays parallel to the ground. if (itsTrackerSlope == 0.0) { // For east-west tracking, the absolute value of the sun-azimuth is checked against the tracker azimuth // This is from Duffie and Beckman Page 22. if (itsTrackerAzimuth == Math.PI / 2 || itsTrackerAzimuth == -Math.PI / 2) { // If the user inputs a minimum tilt less than 0, the tracker is able to face the non-dominant direction, so the surface azimuth will change based on the sun azimuth. // However, if the minimum tilt is greater than zero, the tracker can only face the dominant direction. if (itsMinTilt <= 0) { // Math.Abs is used so that the surface azimuth is set to 0 degrees if the sun azimuth is between -90 and 90, and set to 180 degrees if the sun azimuth is between -180 and -90 or between 90 and 180 if (Math.Abs(SunAzimuth) >= Math.Abs(itsTrackerAzimuth)) { SurfAzimuth = Math.PI; } else { SurfAzimuth = 0; } } else { SurfAzimuth = itsTrackerAzimuth - Math.PI / 2; } } else if (itsTrackerAzimuth == 0) { // For north-south tracking, the sign of the sun-azimuth is checked against the tracker azimuth // This is from Duffie and Beckman Page 22. if (SunAzimuth >= itsTrackerAzimuth) { SurfAzimuth = Math.PI / 2; } else { SurfAzimuth = -Math.PI / 2; } } // Surface slope calculated from eq. 31 of reference guide SurfSlope = Math.Atan2(Math.Sin(SunZenith) * Math.Cos(SurfAzimuth - SunAzimuth), Math.Cos(SunZenith)); // If the shadow is greater than the Pitch and backtracking is selected if (useBackTracking) { if (itsTrackerBW / (Math.Cos(SurfSlope)) > itsTrackerPitch) { // NB: From Lorenzo, Narvarte, and Munoz AngleCorrection = Math.Acos((itsTrackerPitch * (Math.Cos(SurfSlope))) / itsTrackerBW); SurfSlope = SurfSlope - AngleCorrection; } } // Adjusting limits for elevation tracking, so if positive min tilt, the tracker operates within limits properly if (itsTrackerAzimuth == Math.PI / 2 || itsTrackerAzimuth == -Math.PI / 2) { if (itsMinTilt <= 0) { if (Math.Abs(SunAzimuth) <= itsTrackerAzimuth) { SurfSlope = Math.Min(itsMaxTilt, SurfSlope); } else if (Math.Abs(SunAzimuth) > itsTrackerAzimuth) { SurfSlope = Math.Min(Math.Abs(itsMinTilt), SurfSlope); } } else if (itsMinTilt > 0) { SurfSlope = Math.Min(SurfSlope, itsMaxTilt); SurfSlope = Math.Max(SurfSlope, itsMinTilt); } } else if (itsTrackerAzimuth == 0) { SurfSlope = Math.Min(itsMaxTilt, SurfSlope); } } else { // Tilt and Roll double aux = Tilt.GetCosIncidenceAngle(SunZenith, SunAzimuth, itsTrackerSlope, itsTrackerAzimuth); // Equation (7) from Marion and Dobos RotAngle = Math.Atan2((Math.Sin(SunZenith) * Math.Sin(SunAzimuth - itsTrackerAzimuth)), aux); //NB: enforcing rotation limits on tracker RotAngle = Math.Min(itsMaxRotationAngle, RotAngle); RotAngle = Math.Max(itsMinRotationAngle, RotAngle); // Slope from equation (1) in Marion and Dobos SurfSlope = Math.Acos(Math.Cos(RotAngle) * Math.Cos(itsTrackerSlope)); // Surface Azimuth from NREL paper if (SurfSlope != 0) { // Equation (3) in Marion and Dobos if ((-Math.PI <= RotAngle) && (RotAngle < -Math.PI / 2)) { SurfAzimuth = itsTrackerAzimuth - Math.Asin(Math.Sin(RotAngle) / Math.Sin(SurfSlope)) - Math.PI; } // Equation (4) in Marion and Dobos else if ((Math.PI / 2 < RotAngle) && (RotAngle <= Math.PI)) { SurfAzimuth = itsTrackerAzimuth - Math.Asin(Math.Sin(RotAngle) / Math.Sin(SurfSlope)) + Math.PI; } // Equation (2) in Marion and Dobos else { SurfAzimuth = itsTrackerAzimuth + Math.Asin(Math.Sin(RotAngle) / Math.Sin(SurfSlope)); } } //NB: 360 degree correction to put Surface Azimuth into the correct quadrant, see Note 1 if (SurfAzimuth > Math.PI) { SurfAzimuth -= (Math.PI) * 2; } else if (SurfAzimuth < -Math.PI) { SurfAzimuth += (Math.PI) * 2; } } break; // Two Axis Tracking case TrackMode.TAXT: // Defining the surface slope SurfSlope = SunZenith; SurfSlope = Math.Max(itsMinTilt, SurfSlope); SurfSlope = Math.Min(itsMaxTilt, SurfSlope); // Defining the surface azimuth SurfAzimuth = SunAzimuth; // Changes the reference frame to be with respect to the reference azimuth if (SurfAzimuth >= 0) { SurfAzimuth -= itsAzimuthRef; } else { SurfAzimuth += itsAzimuthRef; } // Enforcing the rotation limits with respect to the reference azimuth SurfAzimuth = Math.Max(itsMinAzimuth, SurfAzimuth); SurfAzimuth = Math.Min(itsMaxAzimuth, SurfAzimuth); // Moving the surface azimuth back into the azimuth variable convention if (SurfAzimuth >= 0) { SurfAzimuth -= itsAzimuthRef; } else { SurfAzimuth += itsAzimuthRef; } break; // Azimuth Vertical Axis Tracking case TrackMode.AVAT: // Slope is constant. // Defining the surface azimuth SurfAzimuth = SunAzimuth; // Changes the reference frame to be with respect to the reference azimuth if (SurfAzimuth >= 0) { SurfAzimuth -= itsAzimuthRef; } else { SurfAzimuth += itsAzimuthRef; } // Enforcing the rotation limits with respect to the reference azimuth SurfAzimuth = Math.Max(itsMinAzimuth, SurfAzimuth); SurfAzimuth = Math.Min(itsMaxAzimuth, SurfAzimuth); // Moving the surface azimuth back into the azimuth variable convention if (SurfAzimuth >= 0) { SurfAzimuth -= itsAzimuthRef; } else { SurfAzimuth += itsAzimuthRef; } break; // Fixed Tilt with Seasonal Adjustment // determining if the current timestamp is in the summer or winter season and setting SurfSlope accordingly case TrackMode.FTSA: // SummerDate and WinterDate must be recalculated if year changes due to possible leap year if (previousYear != Year) { SummerDate = new DateTime(Year, itsSummerMonth, itsSummerDay); WinterDate = new DateTime(Year, itsWinterMonth, itsWinterDay); } previousYear = Year; // Winter date is before summer date in calender year if (SummerDate.DayOfYear - WinterDate.DayOfYear > 0) { if (DayOfYear >= WinterDate.DayOfYear && DayOfYear < SummerDate.DayOfYear) { SurfSlope = itsPlaneTiltWinter; } else { SurfSlope = itsPlaneTiltSummer; } } // Summer date is before winter date in calender year else { if (DayOfYear >= SummerDate.DayOfYear && DayOfYear < WinterDate.DayOfYear) { SurfSlope = itsPlaneTiltSummer; } else { SurfSlope = itsPlaneTiltWinter; } } break; case TrackMode.NOAT: break; // Throw error to user if there is an issue with the tracker. default: ErrorLogger.Log("Tracking Parameters were incorrectly defined. Please check your input file.", ErrLevel.FATAL); break; } IncidenceAngle = Tilt.GetIncidenceAngle(SunZenith, SunAzimuth, SurfSlope, SurfAzimuth); }
// Method to start the simulation software run. public void Simulate(XmlDocument SiteSettings, String _Input = null, String _Output = null) { // Adding timer to calculate elapsed time for the simulation. Stopwatch timeTaken = new Stopwatch(); timeTaken.Start(); // Delete error log just before simulation if (File.Exists(Application.StartupPath + "/ErrorLog.txt")) { File.Delete(Application.StartupPath + "/ErrorLog.txt"); } // ReadFarmSettings reads the overall configuration of the farm ReadFarmSettings.doc = SiteSettings; // Obtain IO File names, either from the command prompt, or from .CSYX if ((_Input == null) && (_Output == null)) { ReadFarmSettings.AssignIOFileNames(); } else { ReadFarmSettings.SimInputFile = _Input; ReadFarmSettings.SimOutputFile = _Output; } // Collecting input and output file locations and Input/Output file location ReadFarmSettings.GetSimulationMode(); // Inform user about site configuration: Console.WriteLine("Status: Configuring parameters"); // Creating the SimMeteo Object to process the input file, and streamWriter to Write to the Output File try { // Reading and assigning the input file schema before configuring the inputs ReadFarmSettings.AssignInputFileSchema(); // Instantiating the relevant simulation class based on the simulation mode switch (ReadFarmSettings.SystemMode) { case "ASTME2848Regression": SimASTM = new ASTME2848(); SimASTM.Config(); break; case "GridConnected": // Assign the Sub-Array Count for grid connected systems. ReadFarmSettings.AssignSubArrayCount(); SimGridSys = new GridConnectedSystem(); SimGridSys.Config(); goto case "Radiation"; case "Radiation": SimRadProc = new RadiationProc(); SimRadProc.Config(); break; default: ErrorLogger.Log("Invalid Simulation mode found. CASSYS will exit.", ErrLevel.FATAL); break; } // Reading and assigning the input file schema and the output file schema ReadFarmSettings.AssignOutputFileSchema(); // Notifying user of the Configuration status if (ErrorLogger.numWarnings == 0) { Console.WriteLine("Status: Configuration OK."); } else { Console.WriteLine("Status: There were problems encountered during configuration."); Console.WriteLine(" Please see the error log file for details."); Console.WriteLine(" CASSYS has configured parameters with default values."); } try { // Read through the input file and perform calculations Console.Write("Status: Simulation Running"); // Creating the input file parser SimMeteo SimMeteoParser = new SimMeteo(); SimMeteoParser.Config(); // Create StreamWriter if (ReadFarmSettings.outputMode == "str") { // Assigning Headers to output String OutputString = ReadFarmSettings.OutputHeader.TrimEnd(',') + '\n'; } // Create StreamWriter and write output to .csv file else if (ReadFarmSettings.outputMode == "csv") { // If in batch mode, use the appending overload of StreamWriter if ((File.Exists(ReadFarmSettings.SimOutputFile)) && (ReadFarmSettings.batchMode)) { OutputFileWriter = new StreamWriter(ReadFarmSettings.SimOutputFile, true); } else { // Writing the headers to the new output file. OutputFileWriter = new StreamWriter(ReadFarmSettings.SimOutputFile); // Writing the headers to the new output file. OutputFileWriter.WriteLine(ReadFarmSettings.OutputHeader); } } // The following is executed if using iterative mode else { // Row for output header created only during first simulation run if (ReadFarmSettings.runNumber == 1) { ReadFarmSettings.outputTable.Rows.InsertAt(ReadFarmSettings.outputTable.NewRow(), 0); } // Placing output header in data table ReadFarmSettings.outputTable.Rows[0][ReadFarmSettings.runNumber - 1] = ReadFarmSettings.OutputHeader.TrimEnd(','); } // Read through the Input File and perform the relevant simulation while (!SimMeteoParser.InputFileReader.EndOfData) { SimMeteoParser.Calculate(); // If input file line could not be read, go to next line if (!SimMeteoParser.inputRead) { continue; } // running required calculations based on simulation mode switch (ReadFarmSettings.SystemMode) { case "ASTME2848Regression": SimASTM.Calculate(SimMeteoParser); break; case "GridConnected": SimRadProc.Calculate(SimMeteoParser); SimGridSys.Calculate(SimRadProc, SimMeteoParser); LossDiagram.Calculate(); break; case "Radiation": SimRadProc.Calculate(SimMeteoParser); break; default: ErrorLogger.Log("Invalid Simulation mode found. CASSYS will exit.", ErrLevel.FATAL); break; } // Only create output string in DLL mode, as creating string causes significant lag if (ReadFarmSettings.outputMode == "str") { OutputString += String.Join(",", GetOutputLine()) + "\n"; } // Write to output file else if (ReadFarmSettings.outputMode == "csv") { try { // Assembling and writing the line containing all output values OutputFileWriter.WriteLine(String.Join(",", GetOutputLine())); } catch (IOException ex) { ErrorLogger.Log(ex, ErrLevel.WARNING); } } // Using Iterative Mode: Writes output to a datatable else { // Create row if this is the first simulation run if (ReadFarmSettings.runNumber == 1) { ReadFarmSettings.outputTable.Rows.InsertAt(ReadFarmSettings.outputTable.NewRow(), ReadFarmSettings.outputTable.Rows.Count); } // Write output values to data table ReadFarmSettings.outputTable.Rows[ErrorLogger.iterationCount][ReadFarmSettings.runNumber - 1] = String.Join(",", GetOutputLine()); } } if (ReadFarmSettings.outputMode == "str") { // Trim newline character from the end of output OutputString = OutputString.TrimEnd('\r', '\n'); } // Write output to .csv file else if (ReadFarmSettings.outputMode == "csv") { try { // Clean out the buffer of the writer to ensure all entries are written to the output file. OutputFileWriter.Flush(); OutputFileWriter.Dispose(); SimMeteoParser.InputFileReader.Dispose(); } catch (IOException ex) { ErrorLogger.Log(ex, ErrLevel.WARNING); } } // If simulation done for Grid connected mode then write the calculated loss values to temp output file if (ReadFarmSettings.SystemMode == "GridConnected") { LossDiagram.AssignLossOutputs(); } timeTaken.Stop(); Console.WriteLine(""); Console.WriteLine("Status: Complete. Simulation took " + timeTaken.ElapsedMilliseconds / 1000D + " seconds."); } catch (IOException) { ErrorLogger.Log("The output file name " + ReadFarmSettings.SimOutputFile + " is not accesible or is not available at the location provided.", ErrLevel.FATAL); } } catch (Exception ex) { ErrorLogger.Log("CASSYS encountered an unexpected error: " + ex.Message, ErrLevel.FATAL); } }
double YearsSinceStart; // Number of entire years since the beginning of the simulation // Calculate method public void Calculate ( RadiationProc RadProc, // Radiation related data SimMeteo SimMet // Meteological data from inputfile ) { // Record number of years since start of simulation // This is used to calculate module ageing // Update StartTimeStamp if it has never been initialized if (StartTimeStamp == new DateTime()) { StartTimeStamp = RadProc.TimeStampAnalyzed; } TimeSpan SimDelta = RadProc.TimeStampAnalyzed.Subtract(StartTimeStamp); YearsSinceStart = Math.Floor(SimDelta.Days / 365.25); // Reset Losses for (int i = 0; i < SimPVA.Length; i++) { SimInv[i].LossPMinThreshold = 0; SimInv[i].LossClipping = 0; SimInv[i].LossLowVoltage = 0; SimInv[i].LossHighVoltage = 0; } // Calculating solar panel shading SimShading.Calculate(RadProc.SimSun.Zenith, RadProc.SimSun.Azimuth, RadProc.SimHorizonShading.TDir, RadProc.SimHorizonShading.TDif, RadProc.SimHorizonShading.TRef, RadProc.SimTracker.SurfSlope, RadProc.SimTracker.SurfAzimuth); if (RadProc.SimTracker.useBifacial) { // Calculating shading of ground beneath solar panels SimGround.Calculate(RadProc.SimSun.Zenith, RadProc.SimSun.Azimuth, RadProc.SimTracker.SurfSlope, RadProc.SimTracker.SurfAzimuth, RadProc.SimTracker.SurfClearance, RadProc.SimSplitter.HDir, RadProc.SimSplitter.HDif, RadProc.TimeStampAnalyzed); // Get front reflected diffuse irradiance, since it contributes to the back SimPVA[0].CalcEffectiveIrradiance(SimShading.ShadTDir, SimShading.ShadTDif, SimShading.ShadTRef, SimBackTilter.BGlo, RadProc.SimTilter.IncidenceAngle, SimMet.MonthOfYear, SimSpectral.clearnessCorrection); // Get incidence angle modifiers for components of irradiance SimPVA[0].CalcIAM(out SimBackTilter.IAMDir, out SimBackTilter.IAMDif, out SimBackTilter.IAMRef, RadProc.SimTilterOpposite.IncidenceAngle); // Calculate back side global irradiance SimBackTilter.Calculate(RadProc.SimTracker.SurfSlope, RadProc.SimTracker.SurfClearance, RadProc.SimSplitter.HDif, SimPVA[0].TDifRef, SimMet.HGlo, SimGround.frontGroundGHI, SimGround.rearGroundGHI, SimGround.aveGroundGHI, SimMet.MonthOfYear, SimShading.BackBeamSF, RadProc.SimTilterOpposite.TDir, RadProc.TimeStampAnalyzed); } else { SimBackTilter.BGlo = 0; } // Calculating spectral model effects SimSpectral.Calculate(SimMet.HGlo, RadProc.SimSun.NExtra, RadProc.SimSun.Zenith); try { // Calculate PV Array Output for inputs read in this loop for (int j = 0; j < ReadFarmSettings.SubArrayCount; j++) { // Adjust the IV Curve based on Temperature and Irradiance. SimPVA[j].CalcIVCurveParameters(SimMet.TGlo, SimShading.ShadTDir, SimShading.ShadTDif, SimShading.ShadTRef, SimBackTilter.BGlo, RadProc.SimTilter.IncidenceAngle, SimMet.TAmbient, SimMet.WindSpeed, SimMet.TModMeasured, SimMet.MonthOfYear, SimSpectral.clearnessCorrection); // Check Inverter status to determine if the Inverter is ON or OFF GetInverterStatus(j); // If inverter is off set appropriate variables to 0 and recalculate array in open circuit voltage if (!SimInv[j].isON) { SimInv[j].ACPwrOut = 0; SimInv[j].IOut = 0; SimPVA[j].CalcAtOpenCircuit(); SimPVA[j].Calculate(false, SimPVA[j].Voc, YearsSinceStart); } //performing AC wiring calculations SimACWiring[j].Calculate(SimInv[j]); // Assigning the outputs to the dictionary ReadFarmSettings.Outputlist["SubArray_Current" + (j + 1).ToString()] = SimPVA[j].IOut; ReadFarmSettings.Outputlist["SubArray_Voltage" + (j + 1).ToString()] = SimPVA[j].VOut; ReadFarmSettings.Outputlist["SubArray_Power" + (j + 1).ToString()] = SimPVA[j].POut / 1000; ReadFarmSettings.Outputlist["SubArray_Current_Inv" + (j + 1).ToString()] = SimInv[j].IOut; ReadFarmSettings.Outputlist["SubArray_Voltage_Inv" + (j + 1).ToString()] = SimInv[j].itsOutputVoltage; ReadFarmSettings.Outputlist["SubArray_Power_Inv" + (j + 1).ToString()] = SimInv[j].ACPwrOut / 1000; } // Calculating total farm output and total ohmic loss farmACOutput = 0; farmACOhmicLoss = 0; for (int i = 0; i < SimInv.Length; i++) { farmACOutput += SimInv[i].ACPwrOut; farmACOhmicLoss += SimACWiring[i].ACWiringLoss; } SimTransformer.Calculate(farmACOutput - farmACOhmicLoss); // Calculating outputs that will be assigned for this interval // Shading each component of the Tilted radiaton // Using horizon affected tilted radiation ShadGloLoss = (RadProc.SimTilter.TGlo - SimShading.ShadTGlo) - RadProc.SimHorizonShading.LossGlo; ShadGloFactor = (RadProc.SimTilter.TGlo > 0 ? SimShading.ShadTGlo / RadProc.SimTilter.TGlo : 1); ShadBeamLoss = RadProc.SimHorizonShading.TDir - SimShading.ShadTDir; ShadDiffLoss = RadProc.SimTilter.TDif > 0 ? RadProc.SimHorizonShading.TDif - SimShading.ShadTDif : 0; ShadRefLoss = RadProc.SimTilter.TRef > 0 ? RadProc.SimHorizonShading.TRef - SimShading.ShadTRef : 0; //Calculating total farm level variables. Cleaning them so they are non-cumulative. farmDC = 0; farmDCCurrent = 0; farmDCMismatchLoss = 0; farmDCModuleQualityLoss = 0; farmDCModuleLIDLoss = 0; farmDCModuleAgeingLoss = 0; farmDCOhmicLoss = 0; farmDCSoilingLoss = 0; farmDCTemp = 0; farmTotalModules = 0; farmPNomDC = 0; farmPNomAC = 0; farmACPMinThreshLoss = 0; farmACClippingPower = 0; farmACMaxVoltageLoss = 0; farmACMinVoltageLoss = 0; farmPnom = 0; farmTempLoss = 0; farmRadLoss = 0; for (int i = 0; i < SimPVA.Length; i++) { farmDC += SimPVA[i].POut; farmDCCurrent += SimPVA[i].IOut; farmDCMismatchLoss += Math.Max(0, SimPVA[i].MismatchLoss); farmDCModuleQualityLoss += SimPVA[i].ModuleQualityLoss; farmDCModuleLIDLoss += SimPVA[i].ModuleLIDLoss; farmDCModuleAgeingLoss += SimPVA[i].ModuleAgeingLoss; farmDCOhmicLoss += SimPVA[i].OhmicLosses; farmDCSoilingLoss += SimPVA[i].SoilingLoss; farmDCTemp += SimPVA[i].TModule * SimPVA[i].itsNumModules; farmTotalModules += SimPVA[i].itsNumModules; farmPNomDC += SimPVA[i].itsPNomDCArray; farmPNomAC += SimInv[i].itsPNomArrayAC; farmACPMinThreshLoss += SimInv[i].LossPMinThreshold; farmACClippingPower += SimInv[i].LossClipping; farmACMaxVoltageLoss += SimInv[i].LossHighVoltage; farmACMinVoltageLoss += SimInv[i].LossLowVoltage; farmPnom += (SimPVA[i].itsPNom * SimPVA[i].itsNumModules) * SimPVA[i].RadEff / 1000; farmTempLoss += SimPVA[i].tempLoss; farmRadLoss += SimPVA[i].radLoss; } // Averages all PV Array temperature values farmDCTemp /= farmTotalModules; farmModuleTempAndAmbientTempDiff = farmDCTemp - SimMet.TAmbient; farmDCEfficiency = (RadProc.SimTilter.TGlo > 0 ? farmDC / (RadProc.SimTilter.TGlo * farmArea) : 0) * 100; farmPNomDC = Utilities.ConvertWtokW(farmPNomDC); farmPNomAC = Utilities.ConvertWtokW(farmPNomAC); farmOverAllEff = (RadProc.SimTilter.TGlo > 0 && SimTransformer.POut > 0 ? SimTransformer.POut / (RadProc.SimTilter.TGlo * farmArea) : 0) * 100; farmPR = RadProc.SimTilter.TGlo > 0 && farmPNomDC > 0 && SimTransformer.POut > 0 ? SimTransformer.POut / RadProc.SimTilter.TGlo / farmPNomDC : 0; farmSysIER = (SimTransformer.itsPNom - SimTransformer.POut) / (RadProc.SimTilter.TGlo * 1000); } catch (Exception ce) { ErrorLogger.Log(ce, ErrLevel.FATAL); } // Assigning Outputs for this class. AssignOutputs(); }
// Config manages calculations and initializations that need only to be run once public void Config() { useBifacial = Convert.ToBoolean(ReadFarmSettings.GetInnerText("Bifacial", "UseBifacialModel", ErrLevel.FATAL)); if (useBifacial) { switch (ReadFarmSettings.GetAttribute("O&S", "ArrayType", ErrLevel.FATAL)) { // In all cases, pitch must be normalized to panel slope lengths case "Fixed Tilted Plane": if (String.Compare(ReadFarmSettings.CASSYSCSYXVersion, "0.9.3") >= 0) { itsPanelTilt = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PlaneTiltFix", ErrLevel.FATAL)); } else { itsPanelTilt = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PlaneTilt", ErrLevel.FATAL)); } // itsPitch will be assigned in the below (numRows == 1) conditional itsArrayBW = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "CollBandWidthFix", ErrLevel.FATAL)); itsClearance = Convert.ToDouble(ReadFarmSettings.GetInnerText("Bifacial", "GroundClearance", ErrLevel.FATAL)) / itsArrayBW; // Find number of cell rows on back of array [#] numCellRows = Util.NUM_CELLS_PANEL * int.Parse(ReadFarmSettings.GetInnerText("O&S", "StrInWid", ErrLevel.WARNING, _default: "1")); numRows = 1; break; case "Unlimited Rows": itsPanelTilt = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PlaneTilt", ErrLevel.FATAL)); itsArrayBW = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "CollBandWidth", ErrLevel.FATAL)); itsPitch = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "Pitch", ErrLevel.FATAL)) / itsArrayBW; itsClearance = Convert.ToDouble(ReadFarmSettings.GetInnerText("Bifacial", "GroundClearance", ErrLevel.FATAL)) / itsArrayBW; // Find number of cell rows on back of array [#] numCellRows = Util.NUM_CELLS_PANEL * int.Parse(ReadFarmSettings.GetInnerText("O&S", "StrInWid", ErrLevel.WARNING, _default: "1")); numRows = int.Parse(ReadFarmSettings.GetInnerText("O&S", "RowsBlock", ErrLevel.FATAL)); break; case "Single Axis Elevation Tracking (E-W)": itsArrayBW = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "WActiveSAET", ErrLevel.FATAL)); itsPitch = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PitchSAET", ErrLevel.FATAL)) / itsArrayBW; // Find number of cell rows on back of array [#] numCellRows = Util.NUM_CELLS_PANEL * int.Parse(ReadFarmSettings.GetInnerText("O&S", "StrInWidSAET", ErrLevel.WARNING, _default: "1")); numRows = int.Parse(ReadFarmSettings.GetInnerText("O&S", "RowsBlockSAET", ErrLevel.FATAL)); break; case "Single Axis Horizontal Tracking (N-S)": itsArrayBW = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "WActiveSAST", ErrLevel.FATAL)); itsPitch = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PitchSAST", ErrLevel.FATAL)) / itsArrayBW; // Find number of cell rows on back of array [#] numCellRows = Util.NUM_CELLS_PANEL * int.Parse(ReadFarmSettings.GetInnerText("O&S", "StrInWidSAST", ErrLevel.WARNING, _default: "1")); numRows = int.Parse(ReadFarmSettings.GetInnerText("O&S", "RowsBlockSAST", ErrLevel.FATAL)); break; default: ErrorLogger.Log("Bifacial is not supported for the selected orientation and shading.", ErrLevel.FATAL); break; } structLossFactor = Convert.ToDouble(ReadFarmSettings.GetInnerText("Bifacial", "StructBlockingFactor", ErrLevel.FATAL)); if (numRows == 1) { // Pitch is needed for a single row because of ground patch calculations and geometry. Take value 100x greater than array bandwidth. itsPitch = 100; itsRowType = RowType.SINGLE; } else { itsRowType = RowType.INTERIOR; } // Initialize arrays backGlo = new double[numCellRows]; backDir = new double[numCellRows]; backDif = new double[numCellRows]; backFroRef = new double[numCellRows]; backGroRef = new double[numCellRows]; ConfigAlbedo(); } else { // Allows back irradiance profile output even when bifacial is disabled numCellRows = Util.NUM_CELLS_PANEL; backGlo = new double[numCellRows]; } }
public static void Main(string[] args) { // Showing the Header of the Simulation Program in the Console if (ReadFarmSettings.outputMode != "str") { ReadFarmSettings.ShowHeader(); } // Declaring a new simulation object PVPlant = new Simulation(); SiteSettings = new XmlDocument(); // Load CSYX file try { if (args.Length == 0) { // Set batch mode to true, and Ask user for all the arguments Console.WriteLine("Status: You are running CASSYS in batch mode."); Console.WriteLine("Status: You will need to provide a CASSYS Site File (.CSYX), Input File, and Output File Path."); Console.WriteLine("Enter CASSYS Site (.CSYX) File Path: "); String SimFilePath = Console.ReadLine(); Console.WriteLine("Enter Input file path (.csv file): "); SimInputFilePath = Console.ReadLine(); Console.WriteLine("Enter an Output file path (.csv file, Note: if the file exists this will append your results to the file): "); SimOutputFilePath = Console.ReadLine(); SiteSettings.Load(SimFilePath.Replace("\\", "/")); ErrorLogger.RunFileName = SimFilePath; ErrorLogger.Clean(); } else { // Get .CSYX file name from CMDPrompt, and Load document SiteSettings.Load(args[0]); ErrorLogger.RunFileName = args[0]; } // Assigning Xml document to ReadFarmSettings to allow reading of XML document. Check CSYX version ReadFarmSettings.doc = SiteSettings; ReadFarmSettings.CheckCSYXVersion(); // Variable Parameter mode if (ReadFarmSettings.doc.SelectSingleNode("/Site/Iterations/Iteration1") != null) { variableParameters(args); } else if (args.Length == 1) { // Running the Simulation based on the CASSYS Configuration File provided ReadFarmSettings.batchMode = false; PVPlant.Simulate(SiteSettings); // Show the end of simulation message, window should be kept open for longer ReadFarmSettings.ShowFooter(); } else if (args.Length == 3) { // Set batch mode to true, and Run from command prompt arguments directly ReadFarmSettings.batchMode = true; PVPlant.Simulate(SiteSettings, _Input: args[1], _Output: args[2]); } else if (args.Length == 0) { PVPlant.Simulate(SiteSettings, _Input: SimInputFilePath.Replace("\\", "/"), _Output: SimOutputFilePath.Replace("\\", "/")); } else { // CASSYS needs a site file name to run, so warn the user and exit the program. ErrorLogger.Log("No site file provided for CASSYS to simulate. Please select a valid .CSYX file.", ErrLevel.FATAL); } } catch (Exception) { ErrorLogger.Log("CASSYS was unable to access or load the Site XML file. Simulation has ended.", ErrLevel.FATAL); } }
// Finding and Assigning the Input file style as per the SimSettingsFile public static void AssignInputFileSchema() { try { // Collecting file specific information. Delim = GetInnerText("InputFile", "Delimeter", _Error: ErrLevel.FATAL); Util.AveragedAt = GetInnerText("InputFile", "AveragedAt", _Error: ErrLevel.FATAL); Util.timeFormat = GetInnerText("InputFile", "TimeFormat", _Error: ErrLevel.FATAL); Util.timeStep = double.Parse(GetInnerText("InputFile", "Interval", _Error: ErrLevel.FATAL)); ClimateFileRowsToSkip = int.Parse(GetInnerText("InputFile", "RowsToSkip", _Error: ErrLevel.WARNING, _default: "0")); IncClimateRowsAllowed = int.Parse(GetInnerText("InputFile", "IncorrectClimateRowsAllowed", _Error: ErrLevel.INTERNAL, _default: "0")); TMYType = int.Parse(GetInnerText("InputFile", "TMYType", _default: "-1")); // Initializing the array to use as a holder for column numbers. ClimateRefPos = new int[30]; // Notifying the user of the year change in the dates from the TMY3 file. if (TMYType == 3) { ErrorLogger.Log("This is a TMY3 file. The year will be changed to 1990 to ensure the climate data is in chronological order.", ErrLevel.WARNING); } if (TMYType == 1) { ErrorLogger.Log("This is a EPW file. The year will be changed to 2017 to ensure the climate data is in chronological order.", ErrLevel.WARNING); } // Collecting weather variable locations in the file if ((TMYType != 1) && (TMYType != 2) && (TMYType != 3)) { ClimateRefPos[0] = int.Parse(GetInnerText("InputFile", "TimeStamp", _Error: ErrLevel.FATAL)); UsePOA = Int32.TryParse(GetInnerText("InputFile", "GlobalRad", _Error: ErrLevel.WARNING, _default: "N/A"), out ClimateRefPos[2]); UseGHI = Int32.TryParse(GetInnerText("InputFile", "HorIrradiance", _Error: ErrLevel.WARNING, _default: "N/A"), out ClimateRefPos[1]); tempAmbDefined = Int32.TryParse(GetInnerText("InputFile", "TempAmbient", _Error: ErrLevel.FATAL), out ClimateRefPos[3]); UseMeasuredTemp = Int32.TryParse(GetInnerText("InputFile", "TempPanel", _default: "N/A", _Error: ErrLevel.WARNING), out ClimateRefPos[4]); UseWindSpeed = Int32.TryParse(GetInnerText("InputFile", "WindSpeed", _default: "N/A", _Error: ErrLevel.WARNING), out ClimateRefPos[5]); // Check if Horizontal Irradiance is provided for use in simulation. if (UseGHI) { // Check if Diffuse Measured is defined, if Global Horizontal is provided. UseDiffMeasured = Int32.TryParse(GetInnerText("InputFile", "Hor_Diffuse", _Error: ErrLevel.WARNING), out ClimateRefPos[6]); } // Check if at least, and only one type of Irradiance is available to continue the simulation. if (UsePOA == UseGHI) { // If both tilted, and horizontal are provided. if (UsePOA) { ErrorLogger.Log("Column Numbers for both Global Tilted and Horizontal Irradiance have been provided. Please select one of these inputs to run the simulation.", ErrLevel.FATAL); } else { // If both are not provided. ErrorLogger.Log("You have provided insufficient definitions for irradiance. Please check the Climate File Tab.", ErrLevel.FATAL); } } // Check if at least one type of temperature is available to continue with the simulation. if (tempAmbDefined == false && UseMeasuredTemp == false && string.Compare(SystemMode, "GridConnected") == 0) { ErrorLogger.Log("CASSYS did not find definitions for a temperature column in the Climate File. Please define a measured panel temperature or measured ambient temperature column.", ErrLevel.FATAL); } } } catch (FormatException) { ErrorLogger.Log("The column number for Time Stamp is incorrectly defined. Please check your Input file definition.", ErrLevel.FATAL); } }
// Config will assign parameter variables their values as obtained from the .CSYX file public void Config(int ArrayNum) { itsNomOutputPwr = double.Parse(ReadFarmSettings.GetInnerText("Inverter", "PNomAC", _ArrayNum: ArrayNum)) * 1000; itsMinVoltage = double.Parse(ReadFarmSettings.GetInnerText("Inverter", "Min.V", _ArrayNum: ArrayNum)); itsMaxVoltage = double.Parse(ReadFarmSettings.GetInnerText("Inverter", "Max.V", _ArrayNum: ArrayNum, _default: itsMppWindowMax.ToString())); itsNumInverters = int.Parse(ReadFarmSettings.GetInnerText("Inverter", "NumInverters", _ArrayNum: ArrayNum)); itsOutputVoltage = double.Parse(ReadFarmSettings.GetInnerText("Inverter", "Output", _ArrayNum: ArrayNum)); itsPNomArrayAC = itsNomOutputPwr * itsNumInverters; itsThresholdPwr = double.Parse(ReadFarmSettings.GetInnerText("Inverter", "Threshold", _ArrayNum: ArrayNum)); // Assigning the number of output phases; if (ReadFarmSettings.GetInnerText("Inverter", "Type", _ArrayNum: ArrayNum) == "Tri") { // Tri-phase Inverter outputPhases = 3; } else if (ReadFarmSettings.GetInnerText("Inverter", "Type", _ArrayNum: ArrayNum) == "Bi") { // Bi-phase inverter outputPhases = 2; } else if (ReadFarmSettings.GetInnerText("Inverter", "Type", _ArrayNum: ArrayNum) == "Mono") { outputPhases = 1; } else { ErrorLogger.Log("The Inverter output phase definition was not found. Please check the Inverter in the Inverter database.", ErrLevel.FATAL); } // Assigning the operation type of the Inverter if (ReadFarmSettings.GetInnerText("Inverter", "Oper.", _ArrayNum: ArrayNum) == "MPPT") { itsMPPTracking = true; itsMppWindowMin = double.Parse(ReadFarmSettings.GetInnerText("Inverter", "MinMPP", _ArrayNum: ArrayNum)); itsMppWindowMax = double.Parse(ReadFarmSettings.GetInnerText("Inverter", "MaxMPP", _ArrayNum: ArrayNum)); } else { ErrorLogger.Log("The Inverter does not operate in MPPT according to the configurations. CASSYS does not support these inverters at this time. Simulation has ended.", ErrLevel.FATAL); itsMPPTracking = false; } // Assigning if the Inverter is Bipolar or not if ((ReadFarmSettings.GetInnerText("Inverter", "BipolarInput", _ArrayNum: ArrayNum, _Error: ErrLevel.WARNING, _default: "false") == "Yes") || (ReadFarmSettings.GetInnerText("Inverter", "BipolarInput", _ArrayNum: ArrayNum, _Error: ErrLevel.WARNING, _default: "false") == "True") || (ReadFarmSettings.GetInnerText("Inverter", "BipolarInput", _ArrayNum: ArrayNum, _Error: ErrLevel.WARNING, _default: "false") == "Bipolar inputs")) { isBipolar = true; // itsThresholdPwr = double.Parse(ReadFarmSettings.GetInnerText("Inverter", "Threshold", _ArrayNum: ArrayNum)); } else { isBipolar = false; // itsThresholdPwr = double.Parse(ReadFarmSettings.GetInnerText("Inverter", "Threshold", _ArrayNum: ArrayNum)); } // Inverter Efficiency Curve Configuration threeCurves = Convert.ToBoolean(ReadFarmSettings.GetInnerText("Inverter", "MultiCurve", _ArrayNum: ArrayNum)); // Initialization of efficiency curve arrays itsMedEff[0] = new double[8]; itsMedEff[1] = new double[8]; itsHighEff[0] = new double[8]; itsHighEff[1] = new double[8]; // Initialization of efficiency curve arrays itsOnlyEff[0] = new double[8]; itsOnlyEff[1] = new double[8]; // Configuration of the Efficiency Curves for the Inverter ConfigEffCurves(ArrayNum, threeCurves); if (string.Compare(ReadFarmSettings.CASSYSCSYXVersion, "1.2.0") >= 0 && Convert.ToBoolean(ReadFarmSettings.GetInnerText("System", "ACWiringLossAtSTC", _Error: ErrLevel.FATAL))) { itsMaxSubArrayACEff = 1; } else if (threeCurves) { itsMaxSubArrayACEff = itsMedEff[1][itsMedEff[1].Length - 1] / itsMedEff[0][itsMedEff[0].Length - 1]; } else { itsMaxSubArrayACEff = itsOnlyEff[1][itsOnlyEff[1].Length - 1] / itsOnlyEff[0][itsOnlyEff[0].Length - 1]; } }
// Config manages calculations and initializations that need only to be run once public void Config() { bool useBifacial = Convert.ToBoolean(ReadFarmSettings.GetInnerText("Bifacial", "UseBifacialModel", ErrLevel.FATAL)); if (useBifacial) { // Number of segments into which to divide up the ground [#] numGroundSegs = Util.NUM_GROUND_SEGS; switch (ReadFarmSettings.GetAttribute("O&S", "ArrayType", ErrLevel.FATAL)) { // In all cases, pitch and clearance must be normalized to panel slope lengths case "Fixed Tilted Plane": itsTrackMode = TrackMode.NOAT; if (String.Compare(ReadFarmSettings.CASSYSCSYXVersion, "0.9.3") >= 0) { itsPanelTilt = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PlaneTiltFix", ErrLevel.FATAL)); } else { itsPanelTilt = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PlaneTilt", ErrLevel.FATAL)); } // itsPitch will be assigned in the below (numRows == 1) conditional itsArrayBW = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "CollBandWidthFix", ErrLevel.FATAL)); itsClearance = Convert.ToDouble(ReadFarmSettings.GetInnerText("Bifacial", "GroundClearance", ErrLevel.FATAL)) / itsArrayBW; numRows = 1; break; case "Unlimited Rows": itsTrackMode = TrackMode.NOAT; itsPanelTilt = Util.DTOR * Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PlaneTilt", ErrLevel.FATAL)); itsArrayBW = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "CollBandWidth", ErrLevel.FATAL)); itsPitch = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "Pitch", ErrLevel.FATAL)) / itsArrayBW; itsClearance = Convert.ToDouble(ReadFarmSettings.GetInnerText("Bifacial", "GroundClearance", ErrLevel.FATAL)) / itsArrayBW; numRows = int.Parse(ReadFarmSettings.GetInnerText("O&S", "RowsBlock", ErrLevel.FATAL)); break; case "Single Axis Elevation Tracking (E-W)": itsTrackMode = TrackMode.SAXT; itsArrayBW = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "WActiveSAET", ErrLevel.FATAL)); itsPitch = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PitchSAET", ErrLevel.FATAL)) / itsArrayBW; numRows = int.Parse(ReadFarmSettings.GetInnerText("O&S", "RowsBlockSAET", ErrLevel.FATAL)); break; case "Single Axis Horizontal Tracking (N-S)": itsTrackMode = TrackMode.SAXT; itsArrayBW = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "WActiveSAST", ErrLevel.FATAL)); itsPitch = Convert.ToDouble(ReadFarmSettings.GetInnerText("O&S", "PitchSAST", ErrLevel.FATAL)) / itsArrayBW; numRows = int.Parse(ReadFarmSettings.GetInnerText("O&S", "RowsBlockSAST", ErrLevel.FATAL)); break; default: ErrorLogger.Log("Bifacial is not supported for the selected orientation and shading.", ErrLevel.FATAL); break; } transFactor = Convert.ToDouble(ReadFarmSettings.GetInnerText("Bifacial", "PanelTransFactor", ErrLevel.FATAL)); if (numRows == 1) { // Pitch is needed for a single row because of ground patch calculations and geometry. Take value 100x greater than array bandwidth. itsPitch = 100; itsRowType = RowType.SINGLE; } else { itsRowType = RowType.INTERIOR; } // Initialize arrays frontGroundSH = new int[numGroundSegs]; rearGroundSH = new int[numGroundSegs]; frontSkyViewFactors = new double[numGroundSegs]; rearSkyViewFactors = new double[numGroundSegs]; frontGroundGHI = new double[numGroundSegs]; rearGroundGHI = new double[numGroundSegs]; // Calculate sky view factors for diffuse shading. Stays constant for non-tracking systems, so done here in Config() if (itsTrackMode == TrackMode.NOAT) { CalcSkyViewFactors(); } } }