core/unit.c

Go to the documentation of this file.
00001 
00076 #include <stdlib.h>
00077 #include <string.h>
00078 #include <stdio.h>
00079 #include <errno.h>
00080 #include <ctype.h>
00081 #include <math.h>
00082 
00083 #include "platform.h"
00084 #include "output.h"
00085 #include "unit.h"
00086 #include "exception.h"
00087 #include "globals.h"
00088 #include "find.h"
00089 
00090 /* fundamental physical/economic constants */
00091 static double c = 2.997925e8;       
00092 static double e = 1.602189246e-19;  
00093 static double h = 6.62617636e-34;   
00094 static double k = 1.38066244e-23;   
00095 static double m = 0.910953447e-30;  
00096 static double s = 1.233270e4;       
00097 static int precision = 10;          
00098 static UNIT *unit_list = NULL;
00099 static char filepath[1024] = "unitfile.txt";
00100 static int linenum=0;
00101 
00102 /* locate an underived unit */
00103 UNIT *unit_find_underived(char *unit)
00104 {
00105     UNIT *p;
00106 
00107     /* scan list for existing entry */
00108     for (p=unit_list; p!=NULL; p=p->next)
00109     {
00110         if (strcmp(p->name,unit)==0)
00111             return p;
00112     }
00113     return NULL;
00114 }
00115 
00116 /* define a constant */
00117 int unit_constant(char name, double value)
00118 {
00119     char buffer[2] = {name,'\0'};
00120     if (unit_find_underived(buffer)!=NULL)
00121         throw_exception("%s(%d): constant definition of '%s' failed; unit already defined", filepath, linenum, buffer);
00122     switch (name) {
00123     case 'c':
00124         c=value;
00125         break;
00126     case 'e':
00127         e=value;
00128         break;
00129     case 'h':
00130         h=value;
00131         break;
00132     case 'k':
00133         k=value;
00134         break;
00135     case 'm':
00136         m=value;
00137         break;
00138     case 's':
00139         s=value;
00140         break;
00141     default:
00142         throw_exception("%s(%d): constant '%c' is not valid", filepath,linenum,name);
00143         return 0;
00144     }
00145     return 1;
00146 }
00147 
00148 /* define a primary unit */
00149 int unit_primary(char *name,double c,double e,double h,double k,double m,double s,double a,double b,int prec)
00150 {
00151     UNIT *p = (UNIT*)malloc(sizeof(UNIT));
00152     if (unit_find_underived(name)!=NULL)
00153         throw_exception("%s(%d): primary definition of '%s' failed; unit already defined", filepath, linenum, name);
00154     if (p==NULL)
00155     {
00156         throw_exception("%s(%d): memory allocation failed",filepath,linenum);
00157         return 0;
00158     }
00159     strncpy(p->name,name,sizeof(p->name)-1);
00160     p->c = c;
00161     p->e = e;
00162     p->h = h;
00163     p->k = k;
00164     p->m = m;
00165     p->s = s;
00166     p->a = a;
00167     p->b = b;
00168     p->prec = prec;
00169     p->next = unit_list;
00170     unit_list = p;
00171     return 1;
00172 }
00173 
00174 /* determine the precision of a term */
00175 int unit_precision(char *term)
00176 {
00177     char* p;
00178     char mant[256];
00179 
00180     /* get mantissa */
00181     if ((p=strchr(term,'e'))!=NULL || (p=strchr(term,'E'))!=NULL) {
00182 
00183         /* has exponent */
00184         strncpy(mant, term, p-term);
00185     }
00186     else {
00187         strcpy(mant, term);
00188     }
00189 
00190     return (int)(strlen(mant) - (strchr(mant,'.')!=NULL ? 1 : 0));
00191 }
00192 
00193 /* define a derived unit */
00194 int unit_derived(char *name,char *derivation)
00195 {
00196     double c=0,e=0,h=0,k=0,m=0,s=0,a=0,b=0;
00197     int prec=0;
00198     double scalar=1.0;
00199     char lastOp = '\0', nextOp = '\0';
00200     UNIT *lastUnit = NULL;
00201     char *p=derivation;
00202     if (unit_find_underived(name)!=NULL)
00203         throw_exception("%s(%d): derived definition of '%s' failed; unit already defined", filepath, linenum, name);
00204 
00205     /* extract scalar */
00206     if (sscanf(p,"%lf",&scalar)==1)
00207     {
00208         /* advance pointer */
00209         if ((p=strchr(p,' '))!=NULL) 
00210             p++;
00211 
00212         /* reset pointer */
00213         else
00214             p=derivation;
00215     }
00216 
00217     /* scan for terms */
00218     while (*p!='\0')
00219     {
00220         char term[32];
00221         UNIT *pUnit;
00222 
00223         /* extract operation */
00224         if (sscanf(p,"%[^-*/^+]",term)!=1)
00225             throw_exception("%s(%d): unable to read unit '%s'", filepath,linenum,p);
00226 
00227         /* non-exponential ops */
00228         if (nextOp!='^')
00229         {
00230             /* find unit */
00231             pUnit = unit_find_underived(term);
00232 
00233             if (pUnit==NULL)
00234             {
00235                 double val;
00236                 if (sscanf(term,"%lf",&val)==1)
00237                 {
00238                     static UNIT local;
00239                     if (nextOp=='+' || nextOp=='-') /* bias operation */
00240                     {
00241                         UNIT bias = {"",0,0,0,0,0,0,0,val,unit_precision(term)};
00242                         local = bias;
00243                     }
00244                     else /* scale operation */
00245                     {
00246                         UNIT offset = {"",0,0,0,0,0,0,val,0,unit_precision(term)};
00247                         local = offset;
00248                     }
00249                     pUnit = &local;
00250                 }
00251                 else
00252                     throw_exception("%s(%d): unable to find or derive unit '%s'", filepath,linenum,p);
00253             }
00254         }
00255 
00256         /* advance to next term */
00257         p += strlen(term);
00258 
00259         /* add this term to result */
00260         switch (nextOp) {
00261         case '\0':
00262             /* first term */
00263             if (a==0)
00264             {
00265                 c = pUnit->c;
00266                 e = pUnit->e;
00267                 h = pUnit->h;
00268                 k = pUnit->k;
00269                 m = pUnit->m;
00270                 s = pUnit->s;
00271                 a = pUnit->a * scalar;
00272                 b = pUnit->b;
00273                 prec = pUnit->prec;
00274                 nextOp = '*'; /* in case ^ follows first term */
00275             } /* else last term */
00276             break;
00277         case '*':
00278             c += pUnit->c;
00279             e += pUnit->e;
00280             h += pUnit->h;
00281             k += pUnit->k;
00282             m += pUnit->m;
00283             s += pUnit->s;
00284             a *= pUnit->a;
00285             prec = min(prec,pUnit->prec);
00286             break;
00287         case '/':
00288             c -= pUnit->c;
00289             e -= pUnit->e;
00290             h -= pUnit->h;
00291             k -= pUnit->k;
00292             m -= pUnit->m;
00293             s -= pUnit->s;
00294             a /= pUnit->a;
00295             prec = min(prec,pUnit->prec);
00296             break;
00297         case '+':
00298             b += pUnit->b;
00299             break;
00300         case '-':
00301             b -= pUnit->b;
00302             break;
00303         case '^':
00304             if (lastUnit!=NULL)
00305             {
00306                 int repeat = 0;
00307                 if (sscanf(term,"%d",&repeat)!=1 || repeat<2)
00308                     throw_exception("%s(%d): syntax error at '%s'", filepath,linenum,term);
00309                 repeat--;
00310                 switch(lastOp) {
00311                 case '*':
00312                     c += lastUnit->c*repeat;
00313                     e += lastUnit->e*repeat;
00314                     h += lastUnit->h*repeat;
00315                     k += lastUnit->k*repeat;
00316                     m += lastUnit->m*repeat;
00317                     s += lastUnit->s*repeat;
00318                     a *= pow(lastUnit->a,repeat);
00319                     prec = min(prec,lastUnit->prec);
00320                     break;
00321                 case '/':
00322                     c -= lastUnit->c*repeat;
00323                     e -= lastUnit->e*repeat;
00324                     h -= lastUnit->h*repeat;
00325                     k -= lastUnit->k*repeat;
00326                     m -= lastUnit->m*repeat;
00327                     s -= lastUnit->s*repeat;
00328                     a /= pow(lastUnit->a,repeat);
00329                     prec = min(prec,lastUnit->prec);
00330                     break;
00331                 default:
00332                     throw_exception("%s(%d): ^ not allowed after '%c' at '%s'", filepath,linenum,lastOp,term);
00333                     return 0;
00334                 }
00335             }
00336             break;
00337         default:
00338             throw_exception("%s(%d): '%c' is not recognized at '%s'", filepath,linenum,lastOp,term);
00339             return 0;
00340             break;
00341         }
00342         lastOp = nextOp;
00343         if ((nextOp=*p)!='\0')
00344             p++;
00345         lastUnit = pUnit;
00346     }
00347 
00348     return unit_primary(name,c,e,h,k,m,s,a,b,prec);
00349 }
00350 
00353 void unit_init(void)
00354 {
00355     static int tried=0;
00356     char *glpath = getenv("GLPATH");
00357     FILE *fp = NULL;
00358     char *tpath = find_file(filepath, NULL, 4);
00359     /* try only once */
00360     if (tried)
00361         return;
00362     else
00363         tried = 1;
00364     
00365     if(tpath != NULL)
00366         fp = fopen(tpath, "r");
00367 
00368     /* locate unit file on GLPATH if not found locally */
00369     if (fp==NULL && glpath!=NULL)
00370     {
00371         char envbuf[1024];
00372         char *dir;
00373         strcpy(envbuf,glpath);
00374         dir = strtok(envbuf,";");
00375         while (dir!=NULL)
00376         {
00377             strcpy(filepath,dir);
00378             strcat(filepath,"/unitfile.txt");
00379             fp = fopen(filepath,"r");
00380             if (fp!=NULL)
00381                 break;
00382             dir = strtok(NULL,";");
00383         }
00384     }
00385 
00386     /* unit file not found */
00387     if (fp==NULL)
00388     {
00389         output_error("%s: %s (GLPATH=%s)", filepath, strerror(errno),glpath);
00390         return;
00391     }
00392 
00393     /* scan file */
00394     while (!feof(fp) && !ferror(fp))
00395     {
00396         char buffer[1024], *p;
00397         if (fgets(buffer,sizeof(buffer),fp)==NULL)
00398             break;
00399         linenum++;
00400 
00401         /* wipe comments */
00402         p = strchr(buffer,';');
00403         if (p!=NULL)
00404             *p = '\0';
00405 
00406         /* remove trailing whitespace */
00407         p = buffer+strlen(buffer)-1;
00408         while (iswspace(*p) && p>buffer)
00409             *p--='\0';
00410 
00411         /* ignore blank lines or lines starting with white space*/
00412         if (buffer[0]=='\0' || iswspace(buffer[0]))
00413             continue;
00414 
00415         /* constant definition */
00416         if (buffer[0]=='#') {
00417             double value=0.0;
00418             char name;
00419             if (sscanf(buffer+1,"%c=%lf",&name,&value)!=2) 
00420                 output_error("%s(%d): constant definition '%s' is not valid", filepath,linenum,buffer);
00421             else
00422                 unit_constant(name,value);
00423             continue;
00424         }
00425 
00426         /* scalar definition */
00427         if (buffer[0]=='*')
00428         {
00430             output_warning("%s(%d): scalar '%s' ignored (not supported yet)", filepath,linenum,buffer+1);
00431             continue;
00432         }
00433 
00434         /* primary definition */
00435         {
00436             char name[256];
00437             double c, e, h, k, m, s, a, b;
00438             int prec;
00439             if (sscanf(buffer,"%[^=]=%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%d",name,&c,&e,&h,&k,&m,&s,&a,&b,&prec)==10) 
00440             {
00441                 unit_primary(name,c,e,h,k,m,s,a,b,prec);
00442                 continue;
00443             }
00444         }
00445 
00446         /* derived definition */
00447         {
00448             char name[256];
00449             char derivation[256];
00450             if (sscanf(buffer,"%[^=]=%[-+/*^0-9. %A-Za-z]",name,derivation)==2) 
00451             {
00452                 unit_derived(name,derivation);
00453                 continue;
00454             }
00455         }
00456 
00457         /* not recognized */
00458         throw_exception("%s(%d): unit '%s' is not recognized", filepath,linenum,buffer);
00459     }
00460 
00461     /* check status */
00462     if (ferror(fp))
00463         throw_exception("unitfile: %s", strerror(errno));
00464     else
00465         output_verbose("%s loaded ok", filepath);
00466 
00467     /* done */
00468     fclose(fp);
00469 }
00470 
00474 int unit_convert(char *from, char *to, double *pValue)
00475 {
00476     UNIT *pFrom = unit_find(from);
00477     UNIT *pTo = unit_find(to);
00478     if (pFrom!=NULL && pTo!=NULL)
00479         return unit_convert_ex(pFrom,pTo,pValue);
00480     return 0;
00481 }
00482 
00486 int unit_convert_ex(UNIT *pFrom, UNIT *pTo, double *pValue)
00487 {
00488     if (pTo->c==pFrom->c && pTo->e==pFrom->e && pTo->h==pFrom->h && pTo->k==pFrom->k && pTo->m==pFrom->m && pTo->s==pFrom->s)
00489     {
00490         *pValue = (*pValue-pFrom->b)*pFrom->a/pTo->a + pTo->b;
00491         return 1;
00492     }
00493     else
00494         return 0;
00495 }
00496 
00500 UNIT *unit_find(char *unit) 
00501 {
00502     UNIT *p;
00503 
00504     /* first time */
00505     if (unit_list==NULL) unit_init();
00506 
00507     /* scan list for existing entry */
00508     for (p=unit_list; p!=NULL; p=p->next)
00509     {
00510         if (strcmp(p->name,unit)==0)
00511             return p;
00512     }
00513 
00514     /* derive entry if possible */
00515     if (unit_derived(unit,unit))
00516         return unit_list;
00517     else
00518         return NULL;
00519 }
00520 
00524 int unit_test(void)
00525 {
00526     typedef struct {double value; char *unit;} VALUE;
00527     typedef struct {VALUE from; VALUE to; double precision;} TEST;
00528 #ifndef PI
00529 #define PI 3.141592635
00530 #endif
00531     TEST test[] = {
00532         {1, "ratio",    1, "unit"},
00533         {1, "%",        0.01, "unit"},
00534         {1, "pu",       1, "1/unit"},
00535         {1, "/%",       1, "1/%"},
00536 
00537         {PI, "unit",    3.141592635, "unit"},
00538         {2*PI, "rad",   1, "unit"},
00539         {360, "deg",    1, "unit"},
00540         {400, "grad",   1, "unit"},
00541         {4, "quad",     1, "unit"},
00542         {4*PI, "sr",    1, "unit"},
00543 
00544         {1, "R",        5.0/9.0, "K", 0.001},
00545         {0, "degC",     273.14, "K", 0.001},
00546         {0, "degF",     459.65, "R", 0.001},
00547         {1, "kg",       1000, "g"},
00548         {1, "N",        1, "m*kg/s^2"},
00549         {1, "J",        1, "N*m"},
00550         
00551         {1, "min",      60,"s"},
00552         {1, "h",        60,"min"},
00553         {1, "day",      24,"h"},
00554         {1, "yr",       365,"day"},
00555         {1, "syr",      365.24,"day"},
00556 
00557         {1, "cm",       0.01, "m"},
00558         {1, "mm",       0.1, "cm"},
00559         {1, "km",       1000,"m"},
00560         {1, "in",       2.54, "cm"},
00561         {1, "ft",       12, "in"},
00562         {1, "yd",       3, "ft"},
00563         {1, "mile",     5280, "ft"},
00564 
00565         {1, "sf",       1,"ft^2"},
00566         {1, "sy",       1,"yd^2"},
00567 
00568         {1, "cf",       1,"ft^3"},
00569         {1, "cy",       1,"yd^3"},
00570         {1,"l",         0.001,"m^3"},
00571         {1,"gal",       3.785412, "l"},
00572 
00573         {2.2046, "lb",  1, "kg"},
00574         {1, "tonne",    1000, "kg"},
00575         
00576         {1, "mph",      1, "mile/h"},
00577         {1, "fps",      1, "ft/s"},
00578         {1, "fpm",      1, "ft/min"},
00579         {1, "gps",      1, "gal/s"},
00580         {1, "gpm",      1, "gal/min"},
00581         {1, "gph",      1, "gal/h"},
00582         {1, "cfm",      1, "ft^3/min"},
00583         {1, "ach",      1, "1/h"},
00584         
00585         {1, "Hz",       1, "1/s"},
00586         
00587         {1, "W",        1, "J/s"},
00588         {1, "kW",       1000, "W"},
00589         {1, "MW",       1000, "kW"},
00590         {1, "Wh",       1, "W*h"},
00591         {1, "kWh",      1000, "Wh"},
00592         {1, "MWh",      1000, "kWh"},
00593         {1, "Btu",      0.293, "Wh"},
00594         {1, "kBtu",     1000, "Btu"},
00595         {1, "MBtu",     1000, "kBtu"},
00596         {1, "ton",      12000, "Btu/h"},
00597         {1, "tons",     1, "ton*s"},
00598         {1, "tonh",     1, "ton*h"},
00599         {1, "hp",       746, "W"},
00600         {1, "W",        1, "V*A"},
00601         {1, "C",        1, "A*s"},
00602         {1, "F",        1, "C/V"},
00603         {1, "ohm",      1,"V/A"},
00604         {1, "H",        1,"ohm*s"},
00605     };
00606     int n, failed=0, succeeded=0;
00607     output_verbose("performing units tests");
00608     output_test("BEGIN: units tests");
00609     for (n=0; n<sizeof(test)/sizeof(test[0]); n++)
00610     {
00611         double v = test[n].from.value;
00612         if (test[n].precision==0)
00613             test[n].precision = 1e-4;
00614 
00615         /* forward test */
00616         if (!unit_convert(test[n].from.unit,test[n].to.unit,&v))
00617         {
00618             output_test("FAILED: conversion from %s to %s not possible", test[n].from.unit,test[n].to.unit);
00619             failed++;
00620         }
00621         else if (fabs(v-test[n].to.value)>test[n].precision)
00622         {
00623             output_test("FAILED: incorrect unit conversion %g %s -> %g %s (got %g %s instead)", 
00624                 test[n].from.value,test[n].from.unit,
00625                 test[n].to.value,test[n].to.unit,
00626                 v,test[n].to.unit);
00627             failed++;
00628         }
00629 
00630         /* reverse test */
00631         else if (!unit_convert(test[n].to.unit,test[n].from.unit,&v))
00632         {
00633             output_test("FAILED: conversion from %s to %s not possible", test[n].to.unit,test[n].from.unit);
00634             failed++;
00635         }
00636         else if (fabs(v-test[n].from.value)>test[n].precision)
00637         {
00638             output_test("FAILED: incorrect unit conversion %g %s -> %g %s (got %g %s instead)", 
00639                 test[n].to.value,test[n].to.unit,
00640                 test[n].from.value,test[n].from.unit,
00641                 v,test[n].from.unit);
00642             failed++;
00643         }
00644 
00645         else
00646         {
00647             output_test("SUCCESS: %g %s = %g %s",               
00648                     test[n].from.value,test[n].from.unit,
00649                     test[n].to.value,test[n].to.unit);
00650             succeeded++;
00651         }
00652     }
00653     output_test("END: %d units tested", n);
00654     output_debug("units tested: %d ok, %d failed (see '%s' for details).", succeeded, failed, global_testoutputfile);
00655     return failed;
00656 }
00657 

GridLAB-DTM Version 1.0
An open-source project initiated by the US Department of Energy