commercial/office.cpp

Go to the documentation of this file.
00001 
00057 #include <stdlib.h>
00058 #include <stdio.h>
00059 #include <errno.h>
00060 #include <math.h>
00061 #include "gridlabd.h"
00062 #include "solvers.h"
00063 #include "office.h"
00064 #include "../climate/climate.h"
00065 
00066 /* the object class definition */
00067 CLASS *office::oclass = NULL;
00068 
00069 /* the static default object properties */
00070 office *office::defaults = NULL;
00071 
00072 /* specify passes that are needed */
00073 static PASSCONFIG passconfig = PC_PRETOPDOWN|PC_BOTTOMUP;
00074 
00075 /* specify pass that advances the clock  */
00076 static PASSCONFIG clockpass = PC_BOTTOMUP;
00077 
00078 /* Class registration is only called once to register the class with the core */
00079 office::office(MODULE *module)
00080 {
00081     if (oclass==NULL)
00082     {
00083         oclass = gl_register_class(module,"office",passconfig); 
00084         if (gl_publish_variable(oclass,
00085             PT_double, "occupancy", PADDR(zone.current.occupancy),
00086             PT_double, "floor_height[ft]", PADDR(zone.design.floor_height),
00087             PT_double, "exterior_ua[Btu/degF/h]", PADDR(zone.design.exterior_ua),
00088             PT_double, "interior_ua[Btu/degF/h]", PADDR(zone.design.interior_ua),
00089             PT_double, "interior_mass[Btu/degF]", PADDR(zone.design.interior_mass),
00090             PT_double, "floor_area[sf]", PADDR(zone.design.floor_area),
00091             PT_double, "glazing[sf]", PADDR(zone.design.window_area[0]), PT_SIZE, sizeof(zone.design.window_area)/sizeof(zone.design.window_area[0]),
00092             PT_double, "glazing.north[sf]", PADDR(zone.design.window_area[CP_N]),
00093             PT_double, "glazing.northeast[sf]", PADDR(zone.design.window_area[CP_NE]),
00094             PT_double, "glazing.east[sf]", PADDR(zone.design.window_area[CP_E]),
00095             PT_double, "glazing.southeast[sf]", PADDR(zone.design.window_area[CP_SE]),
00096             PT_double, "glazing.south[sf]", PADDR(zone.design.window_area[CP_S]),
00097             PT_double, "glazing.southwest[sf]", PADDR(zone.design.window_area[CP_SW]),
00098             PT_double, "glazing.west[sf]", PADDR(zone.design.window_area[CP_W]),
00099             PT_double, "glazing.northwest[sf]", PADDR(zone.design.window_area[CP_NW]),
00100             PT_double, "glazing.horizontal[sf]", PADDR(zone.design.window_area[CP_H]),
00101             PT_double, "glazing.coefficient[pu]", PADDR(zone.design.glazing_coeff),
00102 
00103             PT_double, "occupants", PADDR(zone.design.occupants),
00104             PT_char256, "schedule", PADDR(zone.design.schedule),
00105 
00106             PT_double, "air_temperature[degF]", PADDR(zone.current.air_temperature),
00107             PT_double, "mass_temperature[degF]", PADDR(zone.current.mass_temperature),
00108             PT_double, "temperature_change[degF/h]", PADDR(zone.current.temperature_change),
00109             PT_double, "Qh[Btu/h]", PADDR(Qh),
00110             PT_double, "Qs[Btu/h]", PADDR(Qs),
00111             PT_double, "Qi[Btu/h]", PADDR(Qi),
00112             PT_double, "Qz[Btu/h]", PADDR(Qz),
00113 
00114             /* HVAC loads */
00115             PT_enumeration, "hvac.mode", PADDR(zone.hvac.mode),
00116                 PT_KEYWORD, "HEAT", HC_HEAT,
00117                 PT_KEYWORD, "AUX", HC_AUX,
00118                 PT_KEYWORD, "COOL", HC_COOL,
00119                 PT_KEYWORD, "ECON", HC_ECON,
00120                 PT_KEYWORD, "OFF", HC_OFF,
00121             PT_double, "hvac.cooling.balance_temperature[degF]", PADDR(zone.hvac.cooling.balance_temperature),
00122             PT_double, "hvac.cooling.capacity[Btu/h]", PADDR(zone.hvac.cooling.capacity),
00123             PT_double, "hvac.cooling.capacity_perF[Btu/degF/h]",PADDR(zone.hvac.cooling.capacity_perF),
00124             PT_double, "hvac.cooling.design_temperature[degF]", PADDR(zone.hvac.cooling.design_temperature),
00125             PT_double, "hvac.cooling.efficiency[pu]",PADDR(zone.hvac.cooling.efficiency),
00126             PT_double, "hvac.cooling.cop[pu]", PADDR(zone.hvac.cooling.cop),
00127             PT_double, "hvac.heating.balance_temperature[degF]",PADDR(zone.hvac.heating.balance_temperature),
00128             PT_double, "hvac.heating.capacity[Btu/h]",PADDR(zone.hvac.heating.capacity),
00129             PT_double, "hvac.heating.capacity_perF[Btu/degF/h]", PADDR(zone.hvac.heating.capacity_perF),
00130             PT_double, "hvac.heating.design_temperature[degF]", PADDR(zone.hvac.heating.design_temperature),
00131             PT_double, "hvac.heating.efficiency[pu]", PADDR(zone.hvac.heating.design_temperature),
00132             PT_double, "hvac.heating.cop[pu]", PADDR(zone.hvac.heating.cop),
00133 
00134             /* Lighting loads */
00135             PT_double, "lights.capacity[kW]", PADDR(zone.lights.capacity),
00136             PT_double, "lights.fraction[pu]", PADDR(zone.lights.fraction),
00137 
00138             /* Plug loads */
00139             PT_double, "plugs.capacity[kW]", PADDR(zone.plugs.capacity),
00140             PT_double, "plugs.fraction[pu]", PADDR(zone.plugs.fraction),
00141 
00142             /* End-use data */
00143             PT_complex, "demand[kW]", PADDR(zone.total.demand),
00144             PT_complex, "power[kW]", PADDR(zone.total.power),
00145             PT_complex, "energy", PADDR(zone.total.energy),
00146             PT_double, "power_factor", PADDR(zone.total.power_factor),
00147 
00148             PT_complex, "hvac.demand[kW]", PADDR(zone.hvac.enduse.demand),
00149             PT_complex, "hvac.power[kW]", PADDR(zone.hvac.enduse.power),
00150             PT_complex, "hvac.energy", PADDR(zone.hvac.enduse.energy),
00151             PT_double, "hvac.power_factor", PADDR(zone.hvac.enduse.power_factor),
00152 
00153             PT_complex, "lights.demand[kW]", PADDR(zone.lights.enduse.demand),
00154             PT_complex, "lights.power[kW]", PADDR(zone.lights.enduse.power),
00155             PT_complex, "lights.energy", PADDR(zone.lights.enduse.energy),
00156             PT_double, "lights.power_factor", PADDR(zone.lights.enduse.power_factor),
00157 
00158             PT_complex, "plugs.demand[kW]", PADDR(zone.plugs.enduse.demand),
00159             PT_complex, "plugs.power[kW]", PADDR(zone.plugs.enduse.power),
00160             PT_complex, "plugs.energy", PADDR(zone.plugs.enduse.energy),
00161             PT_double, "plugs.power_factor", PADDR(zone.plugs.enduse.power_factor),
00162 
00163             PT_double, "control.cooling_setpoint", PADDR(zone.control.cooling_setpoint),
00164             PT_double, "control.heating_setpoint", PADDR(zone.control.heating_setpoint),
00165             PT_double, "control.setpoint_deadband", PADDR(zone.control.setpoint_deadband),
00166             PT_double, "control.ventilation_fraction", PADDR(zone.control.ventilation_fraction),
00167             PT_double, "control.lighting_fraction", PADDR(zone.control.lighting_fraction),
00168             
00169             NULL)<1) throw("unable to publish properties in "__FILE__);
00170         defaults = this;
00171         memset(defaults,0,sizeof(office));
00172 
00173         /* set default power factors */
00174         zone.lights.enduse.power_factor = 1.0;
00175         zone.plugs.enduse.power_factor = 1.0;
00176         zone.hvac.enduse.power_factor = 1.0;
00177 
00178         /* set default climate to static values */
00179         static double Tout = 59, RHout=0.75, Solar[9]={1, 0.9,0.9, 0.5,0.5, 0,0, 0,1};
00180         zone.current.pTemperature = &Tout;
00181         zone.current.pHumidity = &RHout;
00182         zone.current.pSolar = Solar;
00183 
00184         /* set default thermal conditions */
00185         zone.current.air_temperature = Tout;
00186         zone.current.mass_temperature = Tout;
00187 
00188         /* set default control strategy */
00189         zone.control.heating_setpoint = 70;
00190         zone.control.cooling_setpoint = 75;
00191         zone.control.setpoint_deadband = 1;
00192         zone.control.ventilation_fraction = 0.2;
00193         zone.control.lighting_fraction = 0.5;
00194     }
00195 }
00196 
00197 /* Object creation is called once for each object that is created by the core */
00198 int office::create(void) 
00199 {
00200     memcpy(this,defaults,sizeof(*this));
00202     return 1; /* return 1 on success, 0 on failure */
00203 }
00204 
00205 /* Object initialization is called once after all object have been created */
00206 int office::init(OBJECT *parent)
00207 {
00208     update_control_setpoints();
00209 
00212     /* automatic sizing of equipment */
00213     if (zone.hvac.heating.capacity==0)
00214         zone.hvac.heating.capacity = zone.design.exterior_ua*(zone.control.heating_setpoint-zone.hvac.heating.design_temperature);
00215     if (zone.hvac.cooling.capacity==0)
00216         zone.hvac.cooling.capacity = -zone.design.exterior_ua*(zone.hvac.cooling.design_temperature-zone.control.cooling_setpoint) /* losses */
00217             - (zone.design.window_area[0]+zone.design.window_area[1]+zone.design.window_area[2]+zone.design.window_area[3]+zone.design.window_area[4]
00218                 +zone.design.window_area[5]+zone.design.window_area[6]+zone.design.window_area[7]+zone.design.window_area[8])*100*3.412*zone.design.glazing_coeff /* solar */
00219             - (zone.lights.capacity + zone.plugs.capacity)*3.412;
00220     if (zone.hvac.cooling.cop==0)
00221         zone.hvac.cooling.cop=-3.5;
00222     if (zone.hvac.heating.cop==0)
00223         zone.hvac.heating.cop=1.5;
00224 
00226     OBJECT *hdr = OBJECTHDR(this);
00227 
00228     // link to climate data
00229     static FINDLIST *climates = gl_find_objects(FL_NEW,FT_CLASS,SAME,"climate",FT_END);
00230     if (climates==NULL)
00231         gl_warning("house: no climate data found, using static data");
00232     else if (climates->hit_count>1)
00233         gl_warning("house: %d climates found, using first one defined", climates->hit_count);
00234     if (climates->hit_count>0)
00235     {
00236         OBJECT *obj = gl_find_next(climates,NULL);
00237         if (obj->rank<=hdr->rank)
00238             gl_set_dependent(obj,hdr);
00239         zone.current.pTemperature = (double*)GETADDR(obj,gl_get_property(obj,"temperature"));
00240         zone.current.pHumidity = (double*)GETADDR(obj,gl_get_property(obj,"humidity"));
00241         zone.current.pSolar = (double*)GETADDR(obj,gl_get_property(obj,"solar_flux"));
00242     }
00243 
00245     struct {
00246         char *desc;
00247         bool test;
00248     } map[] = {
00250         {"floor height is not valid", zone.design.floor_height<=0},
00251         {"interior mass is not valid", zone.design.interior_mass<=0},
00252         {"interior UA is not valid", zone.design.interior_ua<=0},
00253         {"exterior UA is not valid", zone.design.exterior_ua<=0},
00254         {"floor area is not valid",zone.design.floor_area<=0},
00255         {"control setpoint deadpoint is invalid", zone.control.setpoint_deadband<=0},
00256         {"heating and cooling setpoints conflict",TheatOn>=TcoolOff},
00257         {"heating balance temperature is not greater than heating design temperature",zone.hvac.heating.design_temperature>=zone.hvac.heating.balance_temperature},
00258         {"cooling balance temperature is not less than cooling design temperature",zone.hvac.cooling.design_temperature<=zone.hvac.cooling.balance_temperature},
00259         {"cooling capacity is not negative", zone.hvac.cooling.capacity>=0},
00260         {"heating capacity is not positive", zone.hvac.heating.capacity<=0},
00261         {"cooling cop is not negative", zone.hvac.cooling.cop>=0},
00262         {"heating cop is not positive", zone.hvac.heating.cop<=0},
00263     };
00264     int i;
00265     for (i=0; i<sizeof(map)/sizeof(map[0]); i++)
00266     {
00267         if (map[i].test)
00268             throw map[i].desc;
00269     }
00270     return 1; /* return 1 on success, 0 on failure */
00271 }
00272 
00273 /* Sync is called when the clock needs to advance on the bottom-up pass */
00274 TIMESTAMP office::presync(TIMESTAMP t0, TIMESTAMP t1) 
00275 {
00276     /* reset the multizone heat transfer */
00277     Qz = 0;
00278     return TS_NEVER;
00279 }
00280 
00281 /* Sync is called when the clock needs to advance on the bottom-up pass */
00282 TIMESTAMP office::sync(TIMESTAMP t0, TIMESTAMP t1) 
00283 {
00284     /* local aliases */
00285     const double &Tout = (*(zone.current.pTemperature));
00286     const double &UA = (zone.design.exterior_ua);
00287     const double &Cm = (zone.design.interior_mass);
00288     const double &Hm = (zone.design.interior_ua);
00289     double &Ti = (zone.current.air_temperature);
00290     double &dTi = (zone.current.temperature_change);
00291     double &Tm = (zone.current.mass_temperature);
00292     HCMODE &mode = (zone.hvac.mode);
00293 
00294     /* advance the thermal state of the building */
00295     const double dt1 = t0>0 ? (double)(t1-t0)*TS_SECOND : 0;
00296     if (dt1>0)
00297     {
00298         const double Ca = 0.2402 * 0.0735 * zone.design.floor_height * zone.design.floor_area;
00299         const double dt = dt1/3600;
00300 
00301         /* update enduses and get internal heat gains */
00302         Qi = update_lighting(dt) + update_plugs(dt);
00303 
00304         /* compute heating/cooling effect */
00305         Qh = update_hvac(dt); 
00306 
00308         Qs = 0; 
00309         int i;
00310         for (i=0; i<9; i++)
00311             Qs += 3.412 * zone.design.window_area[i] * zone.current.pSolar[i]/10;
00312         Qs *= dt;
00313         if (Qs<0)
00314             throw "solar gain is negative?!?";
00315 
00316         /* compute thermal constants */
00317         const double c1 = -(UA + Hm)/Ca;
00318         const double c2 = Hm/Ca;
00319         const double c3 = (Qi + Qh + Qs + Qz + Tout*UA)/Ca;
00320         const double c4 = Hm/Cm;
00321         const double c5 = -c4;
00322         const double p1 = 1/c2;
00323         const double p2 = -(c5+c1)/c2;
00324         const double p3 = c1*c5/c2 - c4;
00325         const double p4 = -c3*c5/c2;
00326 
00327         Teq = p4/p3;
00328         const double ra = 2*p1;
00329         const double rb = -p2/ra;
00330         const double rc = sqrt(p2*p2-4*p1*p3)/ra;
00331         if (!isfinite(rc))
00332             throw "thermal solution does not have any roots";
00333         r1 = rb+rc;
00334         r2 = rb-rc;
00335         if (r1==r2)
00336             throw "thermal solution has only one root";
00337 
00338         /* compute initial condition constants */
00339         dTi = c2*Tm + c1*Ti + c3;
00340         k2 = (dTi-r1*Ti+r1*Teq)/(r2-r1);
00341         k1 = Ti-k2-Teq;
00342 
00343         /* compute the next temperature shift */
00344         const double ker1 = k1*exp(r1*dt);
00345         const double ker2 = k2*exp(r2*dt);
00346         const double Ts = ker1+ker2;
00347         if (!isfinite(Ts))
00348             throw("temperature balance equation failed");
00349 
00350         /* compute the new dependent values */
00351         Ti = Teq+Ts;
00352         dTi = r1*ker1+r2*ker2;
00353         Tm = (dTi-c1*Ti-c3)/c2;
00354 
00355         /* calculate the power consumption */
00356         zone.total.power = zone.lights.enduse.power + zone.plugs.enduse.power + zone.hvac.enduse.power;
00357         zone.total.energy = zone.total.power * dt;
00358     }
00359 
00360     /* determine the temperature of the next event */
00361 #define TPREC 0.01
00362     unsigned int Nevents = 0;
00363     double dt2=(double)TS_NEVER;
00364     Tevent = Teq;
00365     if (Qh<0) /* cooling active */
00366         Tevent = TcoolOff;
00367     else if (Qh>0) /* heating active */
00368         Tevent = TheatOff;
00369     else if (Teq<=TheatOn+TPREC) /* heating deadband */
00370         Tevent = TheatOn;
00371     else if (Teq>=TcoolOn-TPREC) /* cooling deadband */
00372         Tevent = TcoolOn;
00373     else /* equilibrium */
00374         return TS_NEVER;
00375 
00376     /* solve for the time to the next event */
00377     Nevents = dual_decay_solve(&dt2,TPREC,0,dt2,k1,r1,k2,r2,Teq-Tevent);
00378     if (Nevents<0)
00379         throw("dual_decay_solve() failed");
00380     else if (Nevents==0)
00381         return TS_NEVER;
00382     else if (dt2==0) /* first solution is immediate, advance clock 1 tick to get next solution */
00383         dt2=1.0/3600/TS_SECOND; /* advance just 1 tick */
00384     if (dt2==TS_NEVER)
00385         return TS_NEVER;
00386     else
00387         return t1+(TIMESTAMP)(dt2*3600*TS_SECOND); /* return t2>t1 on success, t2=t1 for retry, t2<t1 on failure */
00388 }
00389 
00390 void office::update_control_setpoints()
00391 {
00392     TcoolOn = zone.control.cooling_setpoint + zone.control.setpoint_deadband;
00393     TcoolOff = zone.control.cooling_setpoint - zone.control.setpoint_deadband;
00394     TheatOn = zone.control.heating_setpoint - zone.control.setpoint_deadband;
00395     TheatOff = zone.control.heating_setpoint + zone.control.setpoint_deadband;
00396 }
00397 
00398 double office::update_lighting(double dt)
00399 {
00401     return zone.lights.enduse.power.Mag() * dt;
00402 }
00403 
00404 double office::update_plugs(double dt)
00405 {
00407     return zone.plugs.enduse.power.Mag() * dt;
00408 }
00409 
00410 double office::update_hvac(double dt)
00411 {
00412     const double &Tout = (*(zone.current.pTemperature));
00413     const double Trange = 40;   /* range over which HP works in heating mode */
00414     const double Taux = zone.hvac.heating.balance_temperature-Trange;
00415     const double &Tecon = zone.hvac.cooling.balance_temperature;
00416     const double &TbalHeat = zone.hvac.heating.balance_temperature;
00417     const double &TmaxCool = zone.hvac.cooling.design_temperature;
00418     HCMODE &mode = zone.hvac.mode;
00419 
00420     switch (mode) {
00421     case HC_OFF:
00422         cop = 0;
00423         Qrated = 0;
00424         break;
00425     case HC_HEAT:
00426         cop = 1.0 + (zone.hvac.heating.cop-1)*(Tout-Taux)/Trange;
00427         Qrated = zone.hvac.heating.capacity + zone.hvac.heating.capacity_perF*(zone.hvac.heating.balance_temperature-Tout);
00428         break;
00429     case HC_AUX:
00430         cop = 1.0;
00431         Qrated = zone.hvac.heating.capacity;
00432         break;
00433     case HC_COOL:
00434         cop = -1.0 - (zone.hvac.cooling.cop+1)*(Tout-TmaxCool)/(TmaxCool-Tecon);
00435         Qrated = zone.hvac.cooling.capacity - zone.hvac.cooling.capacity_perF*(Tout-zone.hvac.cooling.balance_temperature);
00436         break;
00437     case HC_ECON:
00438         cop = 0.0;
00439         Qrated = zone.hvac.cooling.capacity;
00440         break;
00441     default:
00442         throw "hvac mode is invalid";
00443         break;
00444     }
00446     if (cop!=0)
00447         zone.hvac.enduse.power.SetPowerFactor(Qrated/cop/1000,zone.hvac.enduse.power_factor);
00448     else
00449         zone.hvac.enduse.power = complex(0,0);
00450     if (zone.hvac.enduse.power.Re()<0)
00451         throw "hvac unit is generating electricity!?!";
00452     else if (!isfinite(zone.hvac.enduse.power.Re()) || !isfinite(zone.hvac.enduse.power.Im()))
00453         throw "hvac power is not finite!?!";
00454     return Qrated*dt;
00455 }
00456 
00457 TIMESTAMP office::plc(TIMESTAMP t0, TIMESTAMP t1)
00458 {
00459 #define Tout (*(zone.current.pTemperature))
00460 #define Ti (zone.current.air_temperature)
00461 #define DUTY_CYCLE 0.5 
00462 #define mode (zone.hvac.mode)
00463 #define Taux (zone.hvac.heating.balance_temperature-40)
00464 #define Tecon (zone.hvac.cooling.balance_temperature)
00465 #define DELAY (TS_SECOND*120)
00466     TIMESTAMP t2=TS_NEVER;
00467 
00468     /* compute the new control temperature */
00469     update_control_setpoints();
00470 
00471     if (Ti<TheatOn) 
00472     {
00473         if (Tout<=Taux)
00474             mode = HC_AUX;
00475         else
00476             mode = HC_HEAT;
00477     }
00478     else if (Ti>TheatOff && Ti<TcoolOff) 
00479         mode = HC_OFF;
00480     else if (Ti>TcoolOn) 
00481     {
00482         if (Tout<=Tecon)
00483             mode = HC_ECON;
00484         else
00485             mode = HC_COOL;
00486     }
00487 
00488     return TS_NEVER;
00489 }
00490 
00492 // IMPLEMENTATION OF CORE LINKAGE
00494 
00495 EXPORT int create_office(OBJECT **obj, OBJECT *parent) 
00496 {
00497     try {
00498         *obj = gl_create_object(office::oclass,sizeof(office));
00499         if (*obj!=NULL)
00500         {
00501             office *my = OBJECTDATA(*obj,office);
00502             gl_set_parent(*obj,parent);
00503             return my->create();
00504         }
00505         return 0;
00506     } catch (char *msg) {
00507         gl_error("create_office: %s", msg);
00508         return 0;
00509     }
00510 }
00511 
00512 EXPORT int init_office(OBJECT *obj, OBJECT *parent) 
00513 {
00514     try {
00515         if (obj!=NULL)
00516             return OBJECTDATA(obj,office)->init(parent);
00517         return 0;
00518     } catch (char *msg) {
00519         gl_error("init_%s(obj=%d;%s): %s", obj->oclass->name, obj->id, obj->name?obj->name:"unnamed", msg);
00520         return 0;
00521     }
00522 }
00523 
00524 EXPORT TIMESTAMP sync_office(OBJECT *obj, TIMESTAMP t1, PASSCONFIG pass)
00525 {
00526     try {
00527         TIMESTAMP t2 = TS_NEVER;
00528         office *my = OBJECTDATA(obj,office);
00529         switch (pass) {
00530         case PC_PRETOPDOWN:
00531             t2 = my->presync(obj->clock,t1);
00532             break;
00533         case PC_BOTTOMUP:
00534             t2 = my->sync(obj->clock,t1);
00535             break;
00536         default:
00537             throw("invalid pass request");
00538             break;
00539         }
00540         if (pass==clockpass)
00541             obj->clock = t1;        
00542         return t2;
00543     } catch (char *msg) {
00544         gl_error("sync_office(obj=%d;%s): %s", obj->id, obj->name?obj->name:"unnamed", msg);
00545         return TS_INVALID; /* halt the clock */
00546     }
00547 }
00548 
00549 EXPORT TIMESTAMP plc_office(OBJECT *obj, TIMESTAMP t1, PASSCONFIG pass)
00550 {
00551     try {
00552         if (pass==PC_BOTTOMUP)
00553             return OBJECTDATA(obj,office)->plc(obj->clock,t1);
00554         else
00555             return TS_NEVER;
00556     } catch (char *msg) {
00557         gl_error("plc_%s(obj=%d;%s): %s", obj->oclass->name, obj->id, obj->name?obj->name:"unnamed", msg);
00558         return TS_INVALID;
00559     }
00560 }
00561 

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