예제 #1
0
        // 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);
        }
예제 #2
0
        // 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;
            }
        }
예제 #3
0
        // 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;
                        }
                    }
                }
            }
        }
예제 #4
0
        // 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();
                }
            }
        }
예제 #5
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");
                }
            }
        }
예제 #6
0
        // 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");
                }
            }
        }
예제 #7
0
        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();
        }
예제 #8
0
        // 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();
                    // Assign the back cell rows number for grid connected systems.
                    ReadFarmSettings.AssignCellRowsNum();
                    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);
            }
        }
예제 #9
0
        // 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;
            }
        }
예제 #10
0
파일: Sun.cs 프로젝트: eemiily/CASSYS
        // 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);
        }
예제 #11
0
        ///////////////////////////////////////////////////////////////////////////////
        // 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);
        }
예제 #12
0
        // 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);
        }
예제 #13
0
        // 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];
            }
        }
예제 #14
0
        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();
        }
예제 #15
0
        // 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]);
                    UseMeasuredAlbedo = Int32.TryParse(GetInnerText("InputFile", "MeasAlbedo", _Error: ErrLevel.FATAL), out ClimateRefPos[7]);

                    // 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);
            }
        }
예제 #16
0
        // 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);
            }
        }
예제 #17
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();
        }