Example #1
0
        // Calculation method
        public void Calculate
        (
            double NDir              // normal direct irradiance [W/m2]
            , double HDif            // horizontal diffuse irradiance [W/m2]
            , double NExtra          // normal extraterrestrial irradiance [W/m2]
            , double SunZenith       // zenith angle of sun [radians]
            , double SunAzimuth      // azimuth angle of sun [radians]
            , double AirMass         // air mass [#]
            , int MonthNum           // The month number of the time stamp [1->12]
        )
        {
            // Calculate direct horizontal if direct normal is provided
            double HDir = NDir * Math.Cos(SunZenith);

            // call Perez et al. algorithm or Hay algorithm
            switch (itsTiltAlgorithm)
            {
            case TiltAlgorithm.PEREZ:
                TGlo = GetTiltCompIrradPerez(out TDir, out TDif, out TRef, HDir, HDif, NExtra, SunZenith, SunAzimuth, AirMass, MonthNum);
                break;

            case TiltAlgorithm.HAY:
                TGlo = GetTiltCompIrradHay(out TDir, out TDif, out TRef, HDir, HDif, NExtra, SunZenith, SunAzimuth, MonthNum);
                break;

            default:
                itsTiltAlgorithm = TiltAlgorithm.HAY;
                break;
            }

            // Compute the incidence angle from the Tilt class
            IncidenceAngle = Tilt.GetIncidenceAngle(SunZenith, SunAzimuth, itsSurfaceSlope, itsSurfaceAzimuth);
        }
Example #2
0
        // Computes the fraction of collectors arranged in rows that will be shaded on the back side at a particular sun position.
        public double GetBackShadedFraction
        (
            double SunZenith                // Zenith angle of sun [radians]
            , double SunAzimuth             // Azimuth angle of sun [radians]
            , double CollectorTilt          // Tilt of the module [radians]
        )
        {
            if (SunZenith > Math.PI / 2)
            {
                BackProfileAng = 0;
                return(0);                  // No shading possible as Sun is set or not risen
            }
            else if (Math.Abs(SunAzimuth - itsCollAzimuth) < Math.PI / 2)
            {
                BackProfileAng = 0;
                return(0);                 // No partial back shading possible as Sun is in front of the collectors
            }
            else
            {
                // Compute profile angle, relative to back of module (see Ref 1.)
                BackProfileAng = Tilt.GetProfileAngle(SunZenith, SunAzimuth, itsCollAzimuth + Math.PI);

                // NB: Added small tolerance since shading limit angle and profile angle are found through different methods and could have a small difference
                if (BackSLA - BackProfileAng <= 0.000001)
                {
                    return(0); // No shading possible as the light reaching the back of the panel is not limited by the proceeding row
                }
                else
                {
                    // Computes the fraction of collectors arranged in rows that will be shaded on the back side at a particular sun position.
                    double AC   = Math.Sin(CollectorTilt) * itsCollBW / Math.Sin(BackSLA);
                    double CAAp = Math.PI - CollectorTilt - BackSLA;
                    double CApA = Math.PI - CAAp - (BackSLA - BackProfileAng);

                    // Length of shaded section
                    double AAp = AC * Math.Sin(BackSLA - BackProfileAng) / Math.Sin(CApA);

                    // Using the Cell based shading model
                    if (useCellBasedShading)
                    {
                        double cellShaded = AAp / (CellSize);               // The number of cells shaded
                        double SF         = 1;                              // The resultant shading factor initialized to 1, modified later// Calculate the Shading fraction based on which cell numbers they are between

                        for (int i = 1; i <= itsNumModTransverseStrings; i++)
                        {
                            if ((cellShaded > cellSetup[i - 1]) && (cellShaded < cellSetup[i]))
                            {
                                SF = Math.Min(((shadingPC[i - 1] + (shadingPC[i] * (cellShaded - cellSetup[i - 1])))), shadingPC[i]);
                            }
                        }
                        return(SF);
                    }
                    else
                    {
                        // Return shaded fraction of the collector bandwidth
                        return(AAp / itsCollBW);
                    }
                }
            }
        }
Example #3
0
        // Calculation method
        public void Calculate
        (
            double NDir              // normal direct irradiance [W/m2]
            , double HDif            // horizontal diffuse irradiance [W/m2]
            , double NExtra          // normal extraterrestrial irradiance [W/m2]
            , double SunZenith       // zenith angle of sun [radians]
            , double SunAzimuth      // azimuth angle of sun [radians]
            , double AirMass         // air mass [#]
            , int MonthNum           // The month number of the time stamp [1->12]
            , double MeasAlbedo      // albedo read from climate file, if available (NaN otherwise)
        )
        {
            // Calculate albedo
            // Read from climate file
            if (ReadFarmSettings.GetAttribute("Albedo", "Frequency", ErrLevel.WARNING) == "From Climate File")
            {
                Albedo = MeasAlbedo;
            }
            // Otherwise, read from monthly array
            else
            {
                Albedo = itsMonthlyAlbedo[MonthNum];
            }

            // Calculate direct horizontal if direct normal is provided
            double HDir = NDir * Math.Cos(SunZenith);

            // call Perez et al. algorithm or Hay algorithm
            switch (itsTiltAlgorithm)
            {
            case TiltAlgorithm.PEREZ:
                TGlo = GetTiltCompIrradPerez(out TDir, out TDif, out TRef, HDir, HDif, NExtra, SunZenith, SunAzimuth, AirMass, MonthNum);
                break;

            case TiltAlgorithm.HAY:
                TGlo = GetTiltCompIrradHay(out TDir, out TDif, out TRef, HDir, HDif, NExtra, SunZenith, SunAzimuth, MonthNum);
                break;

            default:
                itsTiltAlgorithm = TiltAlgorithm.HAY;
                break;
            }

            // Compute the incidence angle from the Tilt class
            IncidenceAngle = Tilt.GetIncidenceAngle(SunZenith, SunAzimuth, itsSurfaceSlope, itsSurfaceAzimuth);
        }
Example #4
0
        // De-transposition method to the be used if the meter and panel tilt do not match
        void PyranoDetranspose(SimMeteo SimMet)
        {
            if (pyranoTilter.NoPyranoAnglesDefined)
            {
                SimTracker.Calculate(SimSun.Zenith, SimSun.Azimuth, SimMet.Year, SimMet.DayOfYear);
                pyranoTilter.itsSurfaceAzimuth = SimTracker.SurfAzimuth;
                pyranoTilter.itsSurfaceSlope   = SimTracker.SurfSlope;
                pyranoTilter.IncidenceAngle    = SimTracker.IncidenceAngle;
            }

            // Lower bound of bisection
            double HGloLo = 0;

            // Higher bound of bisection
            double HGloHi = SimSun.NExtra;

            // Calculating the Incidence Angle for the current setup
            double cosInc = Tilt.GetCosIncidenceAngle(SimSun.Zenith, SimSun.Azimuth, pyranoTilter.itsSurfaceSlope, pyranoTilter.itsSurfaceAzimuth);

            // Trivial case
            if (SimMet.TGlo <= 0)
            {
                SimSplitter.Calculate(SimSun.Zenith, 0, NExtra: SimSun.NExtra);
                pyranoTilter.Calculate(SimSplitter.NDir, SimSplitter.HDif, SimSun.NExtra, SimSun.Zenith, SimSun.Azimuth, SimSun.AirMass, SimMet.MonthOfYear, SimMet.Albedo);
            }
            else if ((SimSun.Zenith > 87.5 * Util.DTOR) || (cosInc <= Math.Cos(87.5 * Util.DTOR)))
            {
                SimMet.HGlo = SimMet.TGlo / ((1 + Math.Cos(pyranoTilter.itsSurfaceSlope)) / 2 + pyranoTilter.itsMonthlyAlbedo[SimMet.MonthOfYear] * (1 - Math.Cos(pyranoTilter.itsSurfaceSlope)) / 2);

                // Forcing the horizontal irradiance to be composed entirely of diffuse irradiance
                SimSplitter.HGlo = SimMet.HGlo;
                SimSplitter.HDif = SimMet.HGlo;
                SimSplitter.NDir = 0;
                SimSplitter.HDir = 0;

                //SimSplitter.Calculate(SimSun.Zenith, HGlo, NExtra: SimSun.NExtra);
                pyranoTilter.Calculate(SimSplitter.NDir, SimSplitter.HDif, SimSun.NExtra, SimSun.Zenith, SimSun.Azimuth, SimSun.AirMass, SimMet.MonthOfYear, SimMet.Albedo);
            }
            // Otherwise, bisection loop
            else
            {
                // Bisection loop
                while (Math.Abs(HGloHi - HGloLo) > 0.01)
                {
                    // Use the central value between the domain to start the bisection, and then solve for TGlo,
                    double HGloAv = (HGloLo + HGloHi) / 2;
                    SimSplitter.Calculate(SimSun.Zenith, _HGlo: HGloAv, NExtra: SimSun.NExtra);
                    pyranoTilter.Calculate(SimSplitter.NDir, SimSplitter.HDif, SimSun.NExtra, SimSun.Zenith, SimSun.Azimuth, SimSun.AirMass, SimMet.MonthOfYear, SimMet.Albedo);
                    double TGloAv = pyranoTilter.TGlo;

                    // Compare the TGloAv calculated from the Horizontal guess to the acutal TGlo and change the bounds for analysis
                    // comparing the TGloAv and TGlo
                    if (TGloAv < SimMet.TGlo)
                    {
                        HGloLo = HGloAv;
                    }
                    else
                    {
                        HGloHi = HGloAv;
                    }
                }
            }

            SimMet.HGlo = SimSplitter.HGlo;

            // This value of the horizontal global should now be transposed to the tilt value from the array.
            Transpose(SimMet);
        }
Example #5
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);
        }
Example #6
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;
                        }
                    }
                }
            }
        }
Example #7
0
        // 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);
        }