commercial/office.cpp

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

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