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
00077 CLASS *office::oclass = NULL;
00078
00079
00080 office *office::defaults = NULL;
00081
00082
00083 static PASSCONFIG passconfig = PC_PRETOPDOWN|PC_BOTTOMUP;
00084
00085
00086 static PASSCONFIG clockpass = PC_BOTTOMUP;
00087
00088
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
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
00145 PT_double, "lights.capacity[kW]", PADDR(zone.lights.capacity),
00146 PT_double, "lights.fraction[pu]", PADDR(zone.lights.fraction),
00147
00148
00149 PT_double, "plugs.capacity[kW]", PADDR(zone.plugs.capacity),
00150 PT_double, "plugs.fraction[pu]", PADDR(zone.plugs.fraction),
00151
00152
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
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
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
00195 zone.current.air_temperature = Tout;
00196 zone.current.mass_temperature = Tout;
00197
00198
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
00208 int office::create(void)
00209 {
00210 memcpy(this,defaults,sizeof(*this));
00212 return 1;
00213 }
00214
00215
00216 int office::init(OBJECT *parent)
00217 {
00218 update_control_setpoints();
00219
00222
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)
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
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
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;
00281 }
00282
00283
00284 TIMESTAMP office::presync(TIMESTAMP t0, TIMESTAMP t1)
00285 {
00286
00287 Qz = 0;
00288 return TS_NEVER;
00289 }
00290
00291
00292 TIMESTAMP office::sync(TIMESTAMP t0, TIMESTAMP t1)
00293 {
00294
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
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
00312 Qi = update_lighting(dt) + update_plugs(dt);
00313
00314
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
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
00349 dTi = c2*Tm + c1*Ti + c3;
00350 k2 = (dTi-r1*Ti+r1*Teq)/(r2-r1);
00351 k1 = Ti-k2-Teq;
00352
00353
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
00361 Ti = Teq+Ts;
00362 dTi = r1*ker1+r2*ker2;
00363 Tm = (dTi-c1*Ti-c3)/c2;
00364
00365
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
00371 #define TPREC 0.01
00372 unsigned int Nevents = 0;
00373 double dt2=(double)TS_NEVER;
00374 Tevent = Teq;
00375 if (Qh<0)
00376 Tevent = TcoolOff;
00377 else if (Qh>0)
00378 Tevent = TheatOff;
00379 else if (Teq<=TheatOn+TPREC)
00380 Tevent = TheatOn;
00381 else if (Teq>=TcoolOn-TPREC)
00382 Tevent = TcoolOn;
00383 else
00384 return TS_NEVER;
00385
00386
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)
00393 dt2=1.0/3600/TS_SECOND;
00394 if (dt2==TS_NEVER)
00395 return TS_NEVER;
00396 else
00397 return t1+(TIMESTAMP)(dt2*3600*TS_SECOND);
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;
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
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
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;
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