inline double waterheater::new_temp_1node(double T0, double delta_t) { // old because this happens in presync and needs previously used demand const double mdot_Cp = Cp * water_demand_old * 60 * RHOWATER / GALPCF; // Btu / degF.lb * gal/hr * lb/cf * cf/gal = Btu / degF.hr if (Cw <= ROUNDOFF || (tank_UA+mdot_Cp) <= ROUNDOFF) return T0; const double c1 = (tank_UA + mdot_Cp) / Cw; const double c2 = (actual_kW()*BTUPHPKW + mdot_Cp*Tinlet + tank_UA*get_Tambient(location)) / (tank_UA + mdot_Cp); // return c2 - (c2 + T0) * exp(c1 * delta_t); // [F] return c2 - (c2 - T0) * exp(-c1 * delta_t); // [F] }
inline double range::new_temp_1node(double T0, double delta_t) { // old because this happens in presync and needs previously used demand const double mdot_Cp = specificheat_food * oven_demand_old * 60 * food_density / GALPCF; // Btu / degF.lb * gal/hr * lb/cf * cf/gal = Btu / degF.hr if (Cw <= ROUNDOFF || (oven_UA+mdot_Cp) <= ROUNDOFF) return T0; const double c1 = (oven_UA + mdot_Cp) / Cw; const double c2 = (actual_kW()*BTUPHPKW + mdot_Cp*Tinlet + oven_UA*get_Tambient(location)) / (oven_UA + mdot_Cp); // return c2 - (c2 + T0) * exp(c1 * delta_t); // [F] return c2 - (c2 - T0) * exp(-c1 * delta_t); // [F] }
inline double waterheater::new_time_1node(double T0, double T1) { const double mdot_Cp = Cp * water_demand * 60 * RHOWATER / GALPCF; if (Cw <= ROUNDOFF) return -1.0; const double c1 = ((actual_kW()*BTUPHPKW + tank_UA * get_Tambient(location)) + mdot_Cp*Tinlet) / Cw; const double c2 = -(tank_UA + mdot_Cp) / Cw; if (fabs(c1 + c2*T1) <= ROUNDOFF || fabs(c1 + c2*T0) <= ROUNDOFF || fabs(c2) <= ROUNDOFF) return -1.0; const double new_time = (log(fabs(c1 + c2 * T1)) - log(fabs(c1 + c2 * T0))) / c2; // [hr] return new_time; }
inline double range::new_time_1node(double T0, double T1) { const double mdot_Cp = specificheat_food * oven_demand * 60 * food_density / GALPCF; if (Cw <= ROUNDOFF) return -1.0; const double c1 = ((actual_kW()*BTUPHPKW + oven_UA * get_Tambient(location)) + mdot_Cp*Tinlet) / Cw; const double c2 = -(oven_UA + mdot_Cp) / Cw; if (fabs(c1 + c2*T1) <= ROUNDOFF || fabs(c1 + c2*T0) <= ROUNDOFF || fabs(c2) <= ROUNDOFF) return -1.0; const double new_time = (log(fabs(c1 + c2 * T1)) - log(fabs(c1 + c2 * T0))) / c2; // [hr] return new_time; }
/* the key to picking the equations apart is that the goal is to calculate the temperature differences relative to the * temperature of the lower node (or inlet temp, if 1node). * cA is the volume change from water draw, heating element, and thermal jacket given a uniformly cold tank * cB is the volume change from the extra energy within the hot water node */ double waterheater::dhdt(double h) { if (/*Tupper*/ Tw - Tlower < ROUNDOFF) return 0.0; // if /*Tupper*/ Tw and Tlower are same then dh/dt = 0.0; // Pre-set some algebra just for efficiency... const double mdot = water_demand * 60 * RHOWATER / GALPCF; // lbm/hr... const double c1 = RHOWATER * Cp * area * (/*Tupper*/ Tw - Tlower); // Btu/ft... // check c1 before dividing by it if (c1 <= ROUNDOFF) return 0.0; //Possible only when /*Tupper*/ Tw and Tlower are very close, and the difference is negligible const double cA = -mdot / (RHOWATER * area) + (actual_kW() * BTUPHPKW + tank_UA * (get_Tambient(location) - Tlower)) / c1; const double cb = (tank_UA / height) * (/*Tupper*/ Tw - Tlower) / c1; // Returns the rate of change of 'h' return cA - cb*h; }
inline double waterheater::new_h_2zone(double h0, double delta_t) { if (delta_t <= ROUNDOFF) return h0; // old because this happens in presync and needs previously used demand const double mdot = water_demand_old * 60 * RHOWATER / GALPCF; // lbm/hr... const double c1 = RHOWATER * Cp * area * (/*Tupper*/ Tw - Tlower); // lb/ft^3 * ft^2 * degF * Btu/lb.degF = lb/lb * ft^2/ft^3 * degF/degF * Btu = Btu/ft // check c1 before division if (fabs(c1) <= ROUNDOFF) return height; // if /*Tupper*/ Tw and Tlower are real close, then the new height is the same as tank height // throw MODEL_NOT_2ZONE; // #define CWATER (0.9994) // BTU/lb/F const double cA = -mdot / (RHOWATER * area) + (actual_kW()*BTUPHPKW + tank_UA * (get_Tambient(location) - Tlower)) / c1; // lbm/hr / lb/ft + kW * Btu.h/kW + const double cb = (tank_UA / height) * (/*Tupper*/ Tw - Tlower) / c1; if (fabs(cb) <= ROUNDOFF) return height; return ((exp(cb * delta_t) * (cA + cb * h0)) - cA) / cb; // [ft] }
/** Water heater synchronization determines the time to next synchronization state and the power drawn since last synch **/ TIMESTAMP waterheater::sync(TIMESTAMP t0, TIMESTAMP t1) { double internal_gain = 0.0; double nHours = (gl_tohours(t1) - gl_tohours(t0))/TS_SECOND; double Tamb = get_Tambient(location); // use re_override to control heat_needed state // runs after thermostat() but before "the usual" calculations if(re_override == OV_ON){ heat_needed = TRUE; } else if(re_override == OV_OFF){ heat_needed = FALSE; } if(Tw > 212.0 - thermostat_deadband){ // if it's trying boil, turn it off! heat_needed = FALSE; is_waterheater_on = 0; } TIMESTAMP t2 = residential_enduse::sync(t0,t1); // Now find our current temperatures and boundary height... // And compute the time to the next transition... //Adjusted because shapers go on sync, not presync set_time_to_transition(); // determine internal gains if (location == INSIDE){ if(this->current_model == ONENODE){ internal_gain = tank_UA * (Tw - get_Tambient(location)); } else if(this->current_model == TWONODE){ internal_gain = tank_UA * (Tw - Tamb) * h / height; internal_gain += tank_UA * (Tlower - Tamb) * (1 - h / height); } } else { internal_gain = 0; } // determine the power used if (heat_needed == TRUE){ /* power_kw */ load.total = (heat_mode == GASHEAT ? gas_fan_power : heating_element_capacity); is_waterheater_on = 1; } else { /* power_kw */ load.total = (heat_mode == GASHEAT ? gas_standby_power : 0.0); is_waterheater_on = 0; } //load.total = load.power = /* power_kw */ load.power; load.power = load.total * load.power_fraction; load.admittance = load.total * load.impedance_fraction; load.current = load.total * load.current_fraction; load.heatgain = internal_gain; waterheater_actual_power = load.power + (load.current + load.admittance * load.voltage_factor )* load.voltage_factor; actual_load = waterheater_actual_power.Re(); if (actual_load != 0.0) { prev_load = actual_load; power_state = PS_ON; } else power_state = PS_OFF; // gl_enduse_sync(&(residential_enduse::load),t1); if(re_override == OV_NORMAL){ if (time_to_transition >= (1.0/3600.0)) // 0.0167 represents one second { TIMESTAMP t_to_trans = (TIMESTAMP)(t1+time_to_transition*3600.0/TS_SECOND); return -(t_to_trans); // negative means soft transition } // less than one second means never else return TS_NEVER; } else { return TS_NEVER; // keep running until the forced state ends } }
/** oven synchronization determines the time to next synchronization state and the power drawn since last synch **/ TIMESTAMP range::sync(TIMESTAMP t0, TIMESTAMP t1) { double internal_gain = 0.0; double nHours = (gl_tohours(t1) - gl_tohours(t0))/TS_SECOND; double Tamb = get_Tambient(location); double dt = gl_toseconds(t0>0?t1-t0:0); if (oven_check == true || remainon == true) time_oven_operation +=dt; if (remainon == false) time_oven_operation=0; enduse_queue_oven += enduse_demand_oven * dt/3600/24; if (t0>TS_ZERO && t1>t0) { // compute the total energy usage in this interval load.energy += load.total * dt/3600.0; } if(re_override == OV_ON){ heat_needed = TRUE; } else if(re_override == OV_OFF){ heat_needed = FALSE; } if(Tw > 212.0 - thermostat_deadband){ // if it's trying boil, turn it off! heat_needed = FALSE; is_range_on = 0; } // determine the power used if (heat_needed == TRUE){ /* power_kw */ load.total = heating_element_capacity * (heat_mode == GASHEAT ? 0.01 : 1.0); is_range_on = 1; } else { /* power_kw */ load.total = 0.0; is_range_on = 0; } TIMESTAMP t2 = residential_enduse::sync(t0,t1); set_time_to_transition(); if (location == INSIDE){ if(this->current_model == ONENODE){ internal_gain = oven_UA * (Tw - get_Tambient(location)); } } else { internal_gain = 0; } dt = update_state(dt, t1); //load.total = load.power = /* power_kw */ load.power; load.power = load.total * load.power_fraction; load.admittance = load.total * load.impedance_fraction; load.current = load.total * load.current_fraction; load.heatgain = internal_gain; range_actual_power = load.power + (load.current + load.admittance * load.voltage_factor )* load.voltage_factor; actual_load = range_actual_power.Re(); if (heat_needed == true) total_power_oven = actual_load; else total_power_oven =0; if (actual_load != 0.0) { prev_load = actual_load; power_state = PS_ON; } else power_state = PS_OFF; // gl_enduse_sync(&(residential_enduse::load),t1); if(re_override == OV_NORMAL){ if (time_to_transition < dt) { if (time_to_transition >= (1.0/3600.0)) // 0.0167 represents one second { TIMESTAMP t_to_trans = (t1+time_to_transition*3600.0/TS_SECOND); return -(t_to_trans); // negative means soft transition } // less than one second means never else return TS_NEVER; } else return (TIMESTAMP)(t1+dt); } else { return TS_NEVER; // keep running until the forced state ends } }
/* the key to picking the equations apart is that the goal is to calculate the temperature differences relative to the * temperature of the lower node (or inlet temp, if 1node). */ double range::dhdt(double h) { if (/*Tupper*/ Tw - Tlower < ROUNDOFF) return 0.0; // if /*Tupper*/ Tw and Tlower are same then dh/dt = 0.0; // Pre-set some algebra just for efficiency... const double mdot = oven_demand * 60 * food_density / GALPCF; // lbm/hr... const double c1 = food_density * specificheat_food * area * (/*Tupper*/ Tw - Tlower); // Btu/ft... if (oven_demand > 0.0) double aaa=1; // check c1 before dividing by it if (c1 <= ROUNDOFF) return 0.0; //Possible only when /*Tupper*/ Tw and Tlower are very close, and the difference is negligible const double cA = -mdot / (food_density * area) + (actual_kW() * BTUPHPKW + oven_UA * (get_Tambient(location) - Tlower)) / c1; const double cb = (oven_UA / height) * (/*Tupper*/ Tw - Tlower) / c1; // Returns the rate of change of 'h' return cA - cb*h; }