/* ASCEND modelling environment
Copyright (C) 2008 Carnegie Mellon University
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*//** @file
Wrapper for sunpos_nrel.c to allow access to the calculation from ASCEND.
*/
/* include the external function API from libascend... */
#include
/* include error reporting API as well, so we can send messages to user */
#include
/* for accessing the DATA instance */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* the code that we're wrapping... */
#include "spa.h"
#ifndef PI
# define PI 3.141592653589793238462
#endif
static ExtBBoxInitFunc sunpos_nrel_prepare;
static ExtBBoxFunc sunpos_nrel_calc;
static ExtBBoxInitFunc julian_day_nrel_prepare;
static ExtBBoxFunc julian_day_nrel_calc;
static const char *sunpos_nrel_help = "\
Calculate sun position using NREL SPA code. Inputs are:\n\
* time (relative to reference time)\n\
* pressure (instantaneous atmospheric pressure)\n\
* temperature (instantaneous absolute atmospheric temperature)\n\
* reference time (Julian Day value expressed as seconds)\n\
The reference time allows this function to use the same time variable as the\
rest of your simulation; the reference time is expected to be pre-calculated\
from a year-month-day calculation (see 'julian_day_nrel' external relation).";
static const char *julian_day_nrel_help = "Calculate the Julian Day from "
"year, month, day, hour, minute, second and timezone inputs. "
"Intended for once-off use in ASCEND models to calculate the time offset "
"eg for the start of a weather file. Acceptable dates are in the range "
"of -2000 BC to AD 6000. All of the inputs should be as 'factor' type "
"variables (to avoid needless time unit conversions), except for the "
"timezone, which should be in time units eg '8{h}'.";
/*------------------------------------------------------------------------------
REGISTRATION FUNCTION
*/
/**
This is the function called from 'IMPORT "johnpye/nrel/sunpos_nrels";'
It sets up the functions contained in this external library
*/
extern
ASC_EXPORT int sunpos_nrel_register(){
int result = 0;
ERROR_REPORTER_HERE(ASC_USER_WARNING,"SUNPOS_NREL is still EXPERIMENTAL. Use with caution.\n");
#define CALCFN(NAME,INPUTS,OUTPUTS) \
result += CreateUserFunctionBlackBox(#NAME \
, NAME##_prepare \
, NAME##_calc /* value */ \
, (ExtBBoxFunc*)NULL /* derivatives not provided yet*/ \
, (ExtBBoxFunc*)NULL /* hessian not provided yet */ \
, (ExtBBoxFinalFunc*)NULL /* finalisation not implemented */ \
, INPUTS,OUTPUTS /* inputs, outputs */ \
, NAME##_help /* help text */ \
, 0.0 \
) /* returns 0 on success */
CALCFN(sunpos_nrel,4,2);
CALCFN(julian_day_nrel,7,1);
#undef CALCFN
if(result){
ERROR_REPORTER_HERE(ASC_PROG_NOTE,"CreateUserFunction result = %d\n",result);
}
return result;
}
#define GET_CHILD_VAL(NAME) \
inst = ChildByChar(data,AddSymbol(#NAME)); \
if(!inst){ \
ERROR_REPORTER_HERE(ASC_USER_ERROR \
,"Couldn't locate '" #NAME "' in DATA, please check usage of SUNPOS."\
);\
return 1;\
}\
if(InstanceKind(inst)!=REAL_CONSTANT_INST){\
ERROR_REPORTER_HERE(ASC_USER_ERROR,"DATA member '" #NAME "' must be a real_constant");\
return 1;\
}\
NAME = RC_INST(inst)->value;
/**
This function is called when the black-box relation is being instantiated.
This just gets the data member and checks that it's valid, and stores
it in the blackbox data field.
*/
static int sunpos_nrel_prepare(struct BBoxInterp *bbox,
struct Instance *data,
struct gl_list_t *arglist
){
struct Instance *inst;
double latitude, longitude, elevation;
/* get the latitude */
GET_CHILD_VAL(latitude);
CONSOLE_DEBUG("Latitude: %0.3f",latitude);
if(latitude > PI/2 || latitude < -PI/2){
ERROR_REPORTER_HERE(ASC_USER_ERROR,"'latitude' is out of allowable range -PI/2 to PI/2.");
return 1;
}
/* get the longitude */
GET_CHILD_VAL(longitude);
CONSOLE_DEBUG("Longitude: %0.3f",longitude);
if(longitude > PI || longitude < -PI){
ERROR_REPORTER_HERE(ASC_USER_ERROR,"'latitude' is out of allowable range -PI to PI.");
return 1;
}
/* get the elevation */
GET_CHILD_VAL(elevation);
CONSOLE_DEBUG("Elevation: %0.3f m",elevation);
if(elevation < -6500000){
ERROR_REPORTER_HERE(ASC_USER_ERROR,"'elevation' is out of allowable range (must be > -6,500 km)");
return 1;
}
spa_data *S = ASC_NEW(spa_data);
S->latitude = latitude * 180/PI;
S->longitude = longitude * 180/PI;
S->elevation = elevation;
S->function = SPA_ZA_JD;
ERROR_REPORTER_HERE(ASC_PROG_NOTE,"Prepared position for sun position.\n");
bbox->user_data = (void *)S;
return 0;
}
#define CALCPREPARE(NIN,NOUT) \
/* a few checks about the input requirements */ \
if(ninputs != NIN)return -1; \
if(noutputs != NOUT)return -2; \
if(inputs==NULL)return -3; \
if(outputs==NULL)return -4; \
if(bbox==NULL)return -5; \
\
/* the 'user_data' in the black box object will contain the */\
/* coefficients required for this fluid; cast it to the required form: */\
const spa_data *sunpos1 = (const spa_data *)bbox->user_data
/**
Evaluation function for 'sunpos'
@return 0 on success
*/
static int sunpos_nrel_calc(struct BBoxInterp *bbox,
int ninputs, int noutputs,
double *inputs, double *outputs,
double *jacobian
){
CALCPREPARE(4,2);
double t, p, T, t_offset;
t = inputs[0]; /* convert from JD seconds to JD days */
p = inputs[1] / 100. /* convert Pa to mbar */;
T = inputs[2] - 273.15 /* convert °C to K */;
t_offset = inputs[3];
spa_data S = *sunpos1;
S.pressure = p;
S.temperature = T;
S.jd = (t + t_offset) / 3600 / 24; /* convert to days */
int res = spa_calculate(&S);
//CONSOLE_DEBUG("Sun position: t = %f JD, p %f mbar, T = %f C: res = %d, az = %f, zen = %f",S.jd, p, T, res, S.azimuth, S.zenith);
/* returned values are in degrees, need to convert back to base SI: radians */
outputs[0] = S.zenith * PI/180.;
outputs[1] = S.azimuth180 * PI/180.;
switch(res){
case 0: break;
case 16: CONSOLE_DEBUG("Calculated julian day (t + offset) = %f is out of permitted range",S.jd); break;
default: CONSOLE_DEBUG("Error code %d returned from spa_calculate",res);
}
/* 0 on success, non-zero is error code from spa_calculate (would prob be input parameters out-of-range) */
return res;
}
/*---------- SUNPOS_JULIAN_DAY ------------*/
static int julian_day_nrel_prepare(struct BBoxInterp *bbox,
struct Instance *data,
struct gl_list_t *arglist
){
bbox->user_data = NULL;
return 0;
}
static int julian_day_nrel_calc(struct BBoxInterp *bbox,
int ninputs, int noutputs,
double *inputs, double *outputs,
double *jacobian
){
CALCPREPARE(7,1);
(void)sunpos1;
int y,mon,d,h,m,s;
double tz;
y = inputs[0]; /* year */
mon = inputs[1]; /* month */
d = inputs[2]; /* day */
h = inputs[3]; /* hour */
m = inputs[4]; /* minute */
s = inputs[5]; /* second */
tz = inputs[6] / 3600.; /* timezone (in seconds, converted here to hours) */
double t = julian_day(y,mon,d, h,m,s, tz) * 3600 * 24;
outputs[0] = t;
return 0;
}