/*============================================================================ * * NAME: stest00.c * * PURPOSE: Exercises the solar position algorithms in 'solpos.c'. * * S_solpos * INPUTS: year, daynum, hour, minute, second, latitude, * longitude, timezone * * OPTIONAL: press DEFAULT 1013.0 (standard pressure) * temp DEFAULT 10.0 (standard temperature) * tilt DEFAULT 0.0 (horizontal panel) * aspect DEFAULT 180.0 (South-facing panel) * month (if the S_DOY function is turned off) * day ( " " " ) * * OUTPUTS: amass, ampress, azim, cosinc, coszen, day, daynum, * elevref, etr, etrn, etrtilt, month, prime, * sbcf, sretr, ssetr, unprime, zenref * * S_init (optional initialization for all input parameters in * the posdata struct) * INPUTS: struct posdata* * OUTPUTS: struct posdata* * * (Note: initializes the required S_solpos INPUTS above * to out-of-bounds conditions, forcing the user to * supply the parameters; initializes the OPTIONAL * S_solpos inputs above to nominal values.) * * S_decode (optional utility for decoding the S_solpos return code) * INPUTS: long int S_solpos return value, struct posdata* * OUTPUTS: text to stderr * * * All variables are defined as members of the struct posdata * in 'solpos00.h'. * * Usage: * In calling program, along with other 'includes', insert: * * #include "solpos00.h" * * Martin Rymes * National Renewable Energy Laboratory * 25 March 1998 * * 28 March 2001 REVISION: SMW changed benchmark numbers to reflect the * February 2001 changes to solpos00.c * *----------------------------------------------------------------------------*/ #include #include #include #include "solpos00.h" /* <-- There is the 'include' I was talking about */ int main ( ) { struct posdata pd, *pdat; /* declare a posdata struct and a pointer for it (if desired, the structure could be allocated dynamically with malloc) */ long retval; /* to capture S_solpos return codes */ /************** Begin demo program **************/ pdat = &pd; /* point to the structure for convenience */ /* Initialize structure to default values. (Optional only if ALL input parameters are initialized in the calling code, which they are not in this example.) */ S_init (pdat); /* I use Atlanta, GA for this example */ pdat->longitude = -84.43; /* Note that latitude and longitude are */ pdat->latitude = 33.65; /* in DECIMAL DEGREES, not Deg/Min/Sec */ pdat->timezone = -5.0; /* Eastern time zone, even though longitude would suggest Central. We use what they use. DO NOT ADJUST FOR DAYLIGHT SAVINGS TIME. */ pdat->year = 1999; /* The year is 1999. */ pdat->daynum = 203; /* July 22nd, the 203'rd day of the year (the algorithm will compensate for leap year, so you just count days). S_solpos can be configured to accept month-day dates; see examples below.) */ /* The time of day (STANDARD time) is 9:45:37 */ pdat->hour = 9; pdat->minute = 45; pdat->second = 37; /* Let's assume that the temperature is 27 degrees C and that the pressure is 1006 millibars. The temperature is used for the atmospheric refraction correction, and the pressure is used for the refraction correction and the pressure-corrected airmass. */ pdat->temp = 27.0; pdat->press = 1006.0; /* Finally, we will assume that you have a flat surface facing southeast, tilted at latitude. */ pdat->tilt = pdat->latitude; /* Tilted at latitude */ pdat->aspect = 135.0; /* 135 deg. = SE */ printf ( "\n" ); printf ( "***** TEST S_solpos: *****\n" ); printf ( "\n" ); retval = S_solpos (pdat); /* S_solpos function call */ S_decode(retval, pdat); /* ALWAYS look at the return code! */ /* Now look at the results and compare with NREL benchmark */ printf ( "Note that your final decimal place values may vary\n" ); printf ( "based on your computer's floating-point storage and your\n" ); printf ( "compiler's mathematical algorithms. If you agree with\n" ); printf ( "NREL's values for at least 5 significant digits, assume it works.\n\n" ); printf ( "Note that S_solpos has returned the day and month for the\n"); printf ( "input daynum. When configured to do so, S_solpos will reverse\n"); printf ( "this input/output relationship, accepting month and day as\n"); printf ( "input and returning the day-of-year in the daynum variable.\n"); printf ("\n" ); printf ( "NREL -> 1999.07.22, daynum 203, retval 0, amass 1.335752, ampress 1.326522\n" ); printf ( "SOLTEST -> %d.%0.2d.%0.2d, daynum %d, retval %ld, amass %f, ampress %f\n", pdat->year, pdat->month, pdat->day, pdat->daynum, retval, pdat->amass, pdat->ampress ); printf ( "NREL -> azim 97.032875, cosinc 0.912569, elevref 48.409931\n" ); printf ( "SOLTEST -> azim %f, cosinc %f, elevref %f\n", pdat->azim, pdat->cosinc, pdat->elevref ); printf ( "NREL -> etr 989.668518, etrn 1323.239868, etrtilt 1207.547363\n" ); printf ( "SOLTEST -> etr %f, etrn %f, etrtilt %f\n", pdat->etr, pdat->etrn, pdat->etrtilt ); printf ( "NREL -> prime 1.037040, sbcf 1.201910, sunrise 347.173431\n" ); printf ( "SOLTEST -> prime %f, sbcf %f, sunrise %f\n", pdat->prime, pdat->sbcf, pdat->sretr ); printf ( "NREL -> sunset 1181.111206, unprime 0.964283, zenref 41.590069\n" ); printf ( "SOLTEST -> sunset %f, unprime %f, zenref %f\n", pdat->ssetr, pdat->unprime, pdat->zenref ); /**********************************************************************/ /* S_solpos configuration examples using the function parameter. Selecting a minimum of functions to meet your needs may result in faster execution. A factor of two difference in execution speed exists between S_GEOM (the minimum configuration) and S_ALL (all variables calculated). [S_DOY is actually the simplest and fastest configuration by far, but it only does the date conversions and bypasses all solar geometry.] If speed is not a consideration, use the default S_ALL configuration implemented by the call to S_init. The bitmasks are defined in S_solpos.h. */ /* 1) Calculate the refraction corrected solar position variables */ pdat->function = S_REFRAC; /* 2) Calculate the shadow band correction factor */ pdat->function = S_SBCF; /* 3) Select both of the above functions (Note that the two bitmasks are 'or-ed' together to produce the desired results): */ pdat->function = ( S_REFRAC | S_SBCF ); /* 4) Modify the above configuration for accepting month and day rather than day-of-year. Note that S_DOY (which controls on the day-of-year interpretation) must be inverted, then 'and-ed' with the other function codes to turn the day-of-year OFF. With the day-of-year bit off, S_solpos expects date input in the form of month and day. */ pdat->function = ( (S_REFRAC | S_SBCF) & ~S_DOY ); pdat->month = 7; pdat->day = 22; /* Also note that S_DOY is the only function that you should attempt to clear in this manner: Other function bitmasks are a composite of more than one mask, which represents an interdependency among functions. Turning off unexpected bits will produce unexpected results. If in the course of your program you need fewer parameters calculated, you should rebuild the function mask from zero using only the required function bitmasks. */ /* 5) Switch back to day-of-year in the above configuration by 'or-ing' with the bitmask */ pdat->function |= S_DOY; pdat->month = -99; /* Now ignore ridiculous month and day */ pdat->day = -99; /* and overwrite them with correct values */ /* ... and back to month-day again, etc.: */ pdat->function &= ~S_DOY; /* To see the effects of different configurations, move the above lines of code to just before the call to S_solpos and examine the results. Note that some of the returned parameters are undefined if the configuration doesn't specify that they be calculated. Thus, you must keep track of what you're calculating if you stray from the S_ALL default configuration. */ /**********************************************************************/ /* Looking at the S_solpos return code In the return code, each bit represents an error in the range of individual input parameters. See the bit definition in S_solpos.h for the position of each error flag. To assure that none of your input variables are out of bounds, the calling program should always look at the S_solpos return code. In this example, the function S_decode fulfills that mandate by examining the code and writing an interpretation to the standard error output. To see the effect of an out of bounds parameter, move the following line to just before the call to S_solpos: */ pdat->year = 99; /* will S_solpos accept a two-digit year? */ /* This causes S_decode to output a descriptive line regarding the value of the input year. [This algorithm is valid only between years 1950 and 2050; hence, explicit four-digit years are required. If your dates are in a two-digit format, S_solpos requires that you make a conversion to an explicit four-digit year.] S_decode (located in the solpos.c file) can serve as a template for building your own decoder to handle errors according to the requirements of your calling application. */ /***********************************************************************/ /* Accessing the individual functions */ /* S_solpos was designed to calculate the output variables using the documented input variables. However, as a matter of advanced programming convenience, the individual functions within S_solpos are accessible to the calling program through the use of the primative L_ masks (these are different from the composite S_ masks used above). However, USE THESE WTTH CAUTION since the calling program must supply ALL parameters required by the function. Because many of these variables are otherwise carefully created internally by S_solpos, the individual functions may not have bounds checking; hence your calling program must do all validation on the function input parameters. By the same reasoning, the return error code (retval) may not have considered all relevant input values, leaving the function vulnerable to computational errors or an abnormal end condition. As with the S_ masks above, the function variable is set to the L_ mask. L_ masks may be ORed if desired. The solpos.h file contains a list of all output and transition variables, the reqired L_ mask, and all variables necessary for the calculation within individual functions. For example, the following code seeks only the amass value. It calls only the airmass routine, which requires nothing but refracted zenith angle and pressure. Normally, the refracted zenith angle is a calculation that depends on many other functions within S_solpos. But here using the L_ mask, we can simply set the refracted zenith angle externally and call the airmass function. */ pdat->function = L_AMASS; /* call only the airmass function */ pdat->press = 1013.0; /* set your own pressure */ /* set up for the output of this example */ printf( "Raw airmass loop:\n"); printf( "NREL -> 37.92 5.59 2.90 1.99 1.55 1.30 1.15 1.06 1.02 1.00\n"); printf( "SOLTEST -> "); /* loop through a number of externally-set refracted zenith angles */ for ( pdat->zenref = 90.0; pdat->zenref >= 0.0; pdat->zenref -= 10.0) { retval = S_solpos( pdat ); /* call solpos */ S_decode( retval, pdat ); /* retval may not be valid */ printf("%5.2f ", pdat->amass ); /* print out the airmass */ } printf("\n"); return 0; }