TIMESTAMP house::sync_thermal(TIMESTAMP t1, double nHours){ DATETIME tv; double t = 0.0; gl_localtime(t1, &tv); Tout = *pTout; Tsolar = get_Tsolar(tv.hour, tv.month, Tair, *pTout); solar_load = 0.0; for (int i = 1; i<9; i++) { solar_load += (gross_wall_area*window_wall_ratio/8.0) * glazing_shgc * pSolar[i]; } double netHeatrate = /*hvac_rated_capacity +*/ tload.heatgain*BTUPHPW + solar_load; double Q1 = M_inv11*Tair + M_inv12*Tmaterials; double Q2 = M_inv21*Tair + M_inv22*Tmaterials; if (nHours > ROUNDOFF) { double q1 = exp(s1*nHours)*(Q1 + BB11*Tsolar/s1 + BB12*netHeatrate/s1) - BB11*Tsolar/s1 - BB12*netHeatrate/s1; double q2 = exp(s2*nHours)*(Q2 - BB11*Tsolar/s2 - BB12*netHeatrate/s2) + BB11*Tsolar/s2 + BB12*netHeatrate/s2; Tair = q1*(s1-A22)/A21 + q2*(s2-A22)/A21; Tmaterials = q1 + q2; } else return TS_NEVER; // calculate constants for solving time "t" to reach Tevent const double W = (Q1 + (BB11*Tsolar)/s1 + BB12*netHeatrate/s1)*(s1-A22)/A21; const double X = (BB11*Tsolar/s1 + BB12*netHeatrate/s1)*(s1-A22)/A21; const double Y = (Q2 - (BB11*Tsolar)/s2 - BB12*netHeatrate/s2)*(s2-A22)/A21; const double Z = (BB11*Tsolar/s2 + BB12*netHeatrate/s2)*(s2-A22)/A21; // end new solution // determine next internal event temperature int n_solutions = 0; double Tevent; const double TcoolOn = cooling_setpoint+thermostat_deadband; const double TcoolOff = cooling_setpoint-thermostat_deadband; const double TheatOn = heating_setpoint-thermostat_deadband; const double TheatOff = heating_setpoint+thermostat_deadband; /* determine the temperature of the next event */ #define TPREC 0.01 if (hvac_rated_capacity < 0.0) Tevent = TcoolOff; else if (hvac_rated_capacity > 0.0) Tevent = TheatOff; else if (Tair <= TheatOn+TPREC) Tevent = TheatOn; else if (Tair >= TcoolOn-TPREC) Tevent = TcoolOn; else return TS_NEVER; #ifdef OLD_SOLVER if (nHours > TPREC) // int dual_decay_solve(double *ans, double prec, double start, double end, int f, double a, double n, double b, double m, double c) n_solutions = dual_decay_solve(&t,TPREC,0.0 ,nHours,W,s1,Y,s2,Z-X-Tevent); Tair = Tevent; if (n_solutions<0) gl_error("house: solver error"); else if (n_solutions == 0) return TS_NEVER; else if (t == 0) t = 1.0/3600.0; // one second return t1+(TIMESTAMP)(t*3600*TS_SECOND); #else t = e2solve(W,s1,Y,s2,Z-X-Tevent); Tair = Tevent; if (isfinite(t)) { return t1+(TIMESTAMP)(t*3600*TS_SECOND); } else return TS_NEVER; #endif }
/* Sync is called when the clock needs to advance on the bottom-up pass */ TIMESTAMP office::sync(TIMESTAMP t0, TIMESTAMP t1) { /* load calculations */ update_lighting(t0,t1); update_plugs(t0,t1); /* local aliases */ const double &Tout = (*(zone.current.pTemperature)); const double &Ua = (zone.design.exterior_ua); const double &Cm = (zone.design.interior_mass); const double &Um = (zone.design.interior_ua); double &Ti = (zone.current.air_temperature); double &dTi = (zone.current.temperature_change); double &Tm = (zone.current.mass_temperature); HCMODE &mode = (zone.hvac.mode); /* advance the thermal state of the building */ const double dt1 = t0>0 ? (double)(t1-t0)*TS_SECOND : 0; if (dt1>0) { const double dt = dt1/3600; /* model operates in units of hours */ /* calculate model update */ if (c2!=0) { /* update temperatures */ const double e1 = k1*exp(r1*dt); const double e2 = k2*exp(r2*dt); Ti = e1 + e2 + Teq; Tm = ((r1-c1)*e1 + (r2-c1)*e2 + c6)/c2 + Teq; if (warn_control) { /* check for air temperature excursion */ if (Ti<warn_low_temp || Ti>warn_high_temp) { OBJECT *obj = OBJECTHDR(this); DATETIME dt0, dt1; gl_localtime(t0,&dt0); gl_localtime(t1,&dt1); char ts0[64], ts1[64]; gl_warning("office:%d (%s) air temperature excursion (%.1f degF) at between %s and %s", obj->id, obj->name?obj->name:"anonymous", Ti, gl_strtime(&dt0,ts0,sizeof(ts0))?ts0:"UNKNOWN", gl_strtime(&dt1,ts1,sizeof(ts1))?ts1:"UNKNOWN"); } /* check for mass temperature excursion */ if (Tm<warn_low_temp || Tm>warn_high_temp) { OBJECT *obj = OBJECTHDR(this); DATETIME dt0, dt1; gl_localtime(t0,&dt0); gl_localtime(t1,&dt1); char ts0[64], ts1[64]; gl_warning("office:%d (%s) mass temperature excursion (%.1f degF) at between %s and %s", obj->id, obj->name?obj->name:"anonymous", Tm, gl_strtime(&dt0,ts0,sizeof(ts0))?ts0:"UNKNOWN", gl_strtime(&dt1,ts1,sizeof(ts1))?ts1:"UNKNOWN"); } } /* calculate the power consumption */ zone.total.energy += zone.total.power * dt; } const double Ca = 0.2402 * 0.0735 * zone.design.floor_height * zone.design.floor_area; /* update enduses and get internal heat gains */ Qi = zone.lights.enduse.heatgain + zone.plugs.enduse.heatgain; /* compute solar gains */ Qs = 0; int i; for (i=0; i<9; i++) Qs += zone.design.window_area[i] * zone.current.pSolar[i]/10; Qs *= 3.412; if (Qs<0) throw "solar gain is negative?!?"; /* compute heating/cooling effect */ Qh = update_hvac(); if (Ca<=0) throw "Ca must be positive"; if (Cm<=0) throw "Cm must be positive"; // split gains to air and mass double f_air = 1.0; /* adjust the fraction of gains that goes to air vs mass */ double Qa = Qh + f_air*(Qi + Qs); double Qm = (1-f_air)*(Qi + Qs); c1 = -(Ua + Um)/Ca; c2 = Um/Ca; c3 = (Qa + Tout*Ua)/Ca; c6 = Qm/Cm; c7 = Qa/Ca; double p1 = 1/c2; if (Cm<=0) throw "Cm must be positive"; c4 = Um/Cm; c5 = -c4; if (c2<=0) throw "Um must be positive"; double p2 = -(c5+c1)/c2; double p3 = c1*c5/c2 - c4; double p4 = -c3*c5/c2 + c6; if (p3==0) throw "Teq is not finite"; Teq = p4/p3; /* compute solution roots */ if (p1==0) throw "internal error (p1==0 -> Ca==0 which should have caught)"; const double ra = 2*p1; const double rb = -p2/ra; const double rr = p2*p2-4*p1*p3; if (rr<0) throw "thermal solution does not exist"; const double rc = sqrt(rr)/ra; r1 = rb+rc; r2 = rb-rc; if (r1>0 || r2>0) throw "thermal solution has runaway condition"; /* compute next initial condition */ dTi = c2*Tm + c1*Ti - (c1+c2)*Tout + c7; k1 = (r2*Ti - r2*Teq - dTi)/(r2-r1); k2 = (dTi - r1*k1)/r2; /* calculate power */ zone.total.power = zone.lights.enduse.power + zone.plugs.enduse.power + zone.hvac.enduse.power; if (warn_control) { /* check for heating equipment sizing problem */ if ((mode==HC_HEAT || mode==HC_AUX) && Teq<TheatOff) { OBJECT *obj = OBJECTHDR(this); DATETIME dt0, dt1; gl_localtime(t0,&dt0); gl_localtime(t1,&dt1); char ts0[64], ts1[64]; gl_warning("office:%d (%s) %s heating undersized between %s and %s", obj->id, obj->name?obj->name:"anonymous", mode==HC_HEAT?"primary":"auxiliary", gl_strtime(&dt0,ts0,sizeof(ts0))?ts0:"UNKNOWN", gl_strtime(&dt1,ts1,sizeof(ts1))?ts1:"UNKNOWN"); } /* check for cooling equipment sizing problem */ else if (mode==HC_COOL && Teq>TcoolOff) { OBJECT *obj = OBJECTHDR(this); DATETIME dt0, dt1; gl_localtime(t0,&dt0); gl_localtime(t1,&dt1); char ts0[64], ts1[64]; gl_warning("office:%d (%s) cooling undersized between %s and %s", obj->id, obj->name?obj->name:"anonymous", mode==HC_COOL?"COOL":"ECON", gl_strtime(&dt0,ts0,sizeof(ts0))?ts0:"UNKNOWN", gl_strtime(&dt1,ts1,sizeof(ts1))?ts1:"UNKNOWN"); } /* check for economizer control problem */ else if (mode==HC_ECON && Teq>TcoolOff) { OBJECT *obj = OBJECTHDR(this); DATETIME dt; gl_localtime(t1,&dt); char ts[64]; gl_warning("office:%d (%s) insufficient economizer control at %s", obj->id, obj->name?obj->name:"anonymous", gl_strtime(&dt,ts,sizeof(ts))?ts:"UNKNOWN"); } } } /* determine the temperature of the next event */ if (Tevent == Teq) return -(t1+(TIMESTAMP)(3600*TS_SECOND)); /* soft return not more than an hour */ /* solve for the time to the next event */ double dt2=(double)TS_NEVER; dt2 = e2solve(k1,r1,k2,r2,Teq-Tevent)*3600; if (isnan(dt2) || !isfinite(dt2) || dt2<0) { if (dTi==0) /* never more than an hour because of the occupancy schedule */ return -(t1+(TIMESTAMP)(3600*TS_SECOND)); /* soft return */ /* do not allow more than 1 degree/hour temperature change before solving again */ dt2 = fabs(3600/dTi); if (dt2>3600) dt2 = 3600; /* never more than an hour because of the occupancy schedule */ return -(t1+(TIMESTAMP)(dt2*TS_SECOND)); /* soft return */ } if (dt2<TS_SECOND) return t1+1; /* need to do a second pass to get next state */ else return t1+(TIMESTAMP)(dt2*TS_SECOND); /* return t2>t1 on success, t2=t1 for retry, t2<t1 on failure */ }