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
00067 CLASS *office::oclass = NULL;
00068
00069
00070 office *office::defaults = NULL;
00071
00072
00073 static PASSCONFIG passconfig = PC_PRETOPDOWN|PC_BOTTOMUP;
00074
00075
00076 static PASSCONFIG clockpass = PC_BOTTOMUP;
00077
00078
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
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
00135 PT_double, "lights.capacity[kW]", PADDR(zone.lights.capacity),
00136 PT_double, "lights.fraction[pu]", PADDR(zone.lights.fraction),
00137
00138
00139 PT_double, "plugs.capacity[kW]", PADDR(zone.plugs.capacity),
00140 PT_double, "plugs.fraction[pu]", PADDR(zone.plugs.fraction),
00141
00142
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
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
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
00185 zone.current.air_temperature = Tout;
00186 zone.current.mass_temperature = Tout;
00187
00188
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
00198 int office::create(void)
00199 {
00200 memcpy(this,defaults,sizeof(*this));
00202 return 1;
00203 }
00204
00205
00206 int office::init(OBJECT *parent)
00207 {
00208 update_control_setpoints();
00209
00212
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)
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
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
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;
00271 }
00272
00273
00274 TIMESTAMP office::presync(TIMESTAMP t0, TIMESTAMP t1)
00275 {
00276
00277 Qz = 0;
00278 return TS_NEVER;
00279 }
00280
00281
00282 TIMESTAMP office::sync(TIMESTAMP t0, TIMESTAMP t1)
00283 {
00284
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
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
00302 Qi = update_lighting(dt) + update_plugs(dt);
00303
00304
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
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
00339 dTi = c2*Tm + c1*Ti + c3;
00340 k2 = (dTi-r1*Ti+r1*Teq)/(r2-r1);
00341 k1 = Ti-k2-Teq;
00342
00343
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
00351 Ti = Teq+Ts;
00352 dTi = r1*ker1+r2*ker2;
00353 Tm = (dTi-c1*Ti-c3)/c2;
00354
00355
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
00361 #define TPREC 0.01
00362 unsigned int Nevents = 0;
00363 double dt2=(double)TS_NEVER;
00364 Tevent = Teq;
00365 if (Qh<0)
00366 Tevent = TcoolOff;
00367 else if (Qh>0)
00368 Tevent = TheatOff;
00369 else if (Teq<=TheatOn+TPREC)
00370 Tevent = TheatOn;
00371 else if (Teq>=TcoolOn-TPREC)
00372 Tevent = TcoolOn;
00373 else
00374 return TS_NEVER;
00375
00376
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)
00383 dt2=1.0/3600/TS_SECOND;
00384 if (dt2==TS_NEVER)
00385 return TS_NEVER;
00386 else
00387 return t1+(TIMESTAMP)(dt2*3600*TS_SECOND);
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;
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
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
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;
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