@@ -970,6 +970,9 @@ RC DHWHEATER::wh_CkF() // water heater input check / default
970970 rc |= requireN ( whenTy, DHWHEATER_UEF, DHWHEATER_RATEDFLOW, DHWHEATER_ANNUALFUEL,
971971 DHWHEATER_ANNUALELEC, DHWHEATER_EFF, 0 );
972972 ignoreN ( whenTy, DHWHEATER_EF, 0 );
973+ if (wh_UEF > wh_eff)
974+ rc |= oer ( " whEff (%0.3f) must be >= whUEF (%0.3f)" ,
975+ wh_eff, wh_UEF);
973976 // note wh_vol check below (wh_vol=0 OK, else error)
974977 }
975978 else if (wh_heatSrc == C_WHHEATSRCCH_ASHP
@@ -1712,6 +1715,69 @@ RC DHWHEATER::wh_InstUEFInit() // one-time setup for UEF-based instantaneous mo
17121715// returns RCOK on success
17131716// else other -- do not run
17141717{
1718+ #if 1
1719+ // parameters used in deriving runtime coefficients
1720+ struct UEFPARAMS
1721+ { float flowMax; float flowRE;
1722+ float A; float B; float qOutUEF; float qOutRE; float durRE; float honUEF;
1723+ };
1724+ static const UEFPARAMS UEFParams[] = {
1725+ // size flowMax flowRE A B qOutUEF qOutRE durRE honUEF
1726+ { /* very small*/ 0 .f , 1 .f , 10 .f , 5 .3500f , 5452 .f , 1090 .f , 2 .000f , 0 .1667f },
1727+ { /* low*/ 1 .7f , 1 .7f , 22 .3529f , 9 .0882f , 20718 .f , 8178 .f , 8 .824f , 0 .4275f },
1728+ { /* medium*/ 2 .8f , 1 .7f , 32 .3529f , 9 .6020f , 29987 .f , 8178 .f , 8 .824f , 0 .5941f },
1729+ { /* high*/ 4 .f , 3 .f , 28 .00f , 9 .7902f , 45798 .f , 14721 .f , 9 .000f , 0 .6542f },
1730+ { /* terminator*/ 9999999 .f }};
1731+
1732+ RC rc = RCOK;
1733+
1734+ // determine size bin
1735+ int iSize;
1736+ for (iSize=0 ; iSize < 4 ; iSize++)
1737+ { if (UEFParams[ iSize+1 ].flowMax > wh_ratedFlow)
1738+ break ;
1739+ }
1740+ const UEFPARAMS& UP = UEFParams[ iSize];
1741+
1742+ // fraction of energy use that is fuel
1743+ double F = 0.99 ;
1744+ if (wh_annualFuel > 0 .f ) // insurance
1745+ F = 1 ./(1 . + (wh_annualElec/wh_annualFuel)*3412 ./100000 .);
1746+
1747+ // electrical power during operation (recovery), W
1748+ double Pe = (wh_annualElec * 1000 ./365 . - wh_stbyElec * (24 .-UP.honUEF ))
1749+ / UP.honUEF ;
1750+ if (Pe < 0 )
1751+ rc |= oer ( " Inconsistent input for whAnnualElec (=%.2f kWh) and whStbyElec (=%.2f W).\n "
1752+ " Derived operating electrical power is < 0." ,
1753+ wh_annualElec, wh_stbyElec);
1754+
1755+ float P = (UP.qOutUEF /wh_UEF - UP.B *UP.qOutRE /wh_eff) / (UP.A - UP.B *UP.durRE );
1756+ float Lcyc = UP.qOutRE /wh_eff - P*UP.durRE ;
1757+
1758+ float Pf = F * P; // fuel at flow=flowRE and deltaT=67, Btu/min
1759+ wh_cycLossFuel = F * Lcyc; // startup fuel, Btu/cycle
1760+
1761+ // max flow per tick, gal-F/tick
1762+ // Top.tp_subhrTickDur = tick duration, min
1763+ wh_maxFlowX = wh_ratedFlow * Top.tp_subhrTickDur * 67 .f ;
1764+
1765+ // maximum load carry forward, Btu
1766+ // user input wh_loadCFwdF = multiplier for rated capacity
1767+ // (approximately allowed catch-up time, hr)
1768+ wh_loadCFwdMax = wh_loadCFwdF * wh_ratedFlow * 8.345 * 67 . * 60 .;
1769+
1770+ // fuel input: Btu/tick at flow=maxFlow and deltaT=67
1771+ wh_maxInpX = Pf // Btu/min at flow=flowRE and deltaT=67
1772+ * (wh_ratedFlow / UP.flowRE ) // scale to max flow
1773+ * Top.tp_subhrTickDur ; // scale to actual tick duration
1774+
1775+ // no electricity use pending model development
1776+ wh_operElec = Pe * BtuperWh; // electrical power during opration, Btuh
1777+ // wh_cycLossElec = 0.f; // electricity use per start, Btu
1778+ // unused in revised model 5-24-2017
1779+
1780+ #else
17151781// parameters used in deriving runtime coefficients
17161782struct UEFPARAMS
17171783{ float flowMax; float flowRE;
@@ -1766,6 +1832,7 @@ static const UEFPARAMS UEFParams[] = {
17661832 wh_operElec = Pe * BtuperWh; // electrical power during opration, Btuh
17671833 // wh_cycLossElec = 0.f; // electricity use per start, Btu
17681834 // unused in revised model 5-24-2017
1835+ #endif
17691836 return rc;
17701837} // DHWHEATER::wh_InstUEFInit
17711838// ----------------------------------------------------------------------------
@@ -1785,11 +1852,58 @@ RC DHWHEATER::wh_InstUEFDoSubhr(
17851852
17861853 float deltaT = max ( 1 ., pWS->ws_tUse - pWS->ws_tInlet ); // temp rise, F
17871854 // max( 1, dT) to prevent x/0
1788- double drawLoss = xLoss / (8.345 *deltaT);
1855+ double qPerGal = 8.345 * deltaT;
1856+ double drawLoss = xLoss / qPerGal;
17891857
17901858 // max vol that can be heated in 1 tick
17911859 double drawFullLoad = wh_maxFlowX / deltaT;
17921860
1861+ #if 1
1862+ // carry-forward: instantaneous heaters throttle flow to maintain temp.
1863+ // here represented by carrying forward a limited amount of unmet load
1864+ wh_HPWHxBU = 0 .; // heat in excess of capacity (after carry-forward)
1865+ // provided by virtual resistance heat
1866+ // prevents gaming compliance via undersizing
1867+ int nTickNZDraw = 0 ; // count of ticks with draw > 0
1868+ double nTickFullLoad = 0 .; // fractional ticks of equiv full load
1869+ double nColdStarts = 0 .; // # of cold startups
1870+ for (int iT=0 ; !rc && iT<Top.tp_nSubhrTicks ; iT++)
1871+ { double drawForTick =
1872+ draw[ iT]*scale*wh_mixDownF
1873+ + drawLoss
1874+ + wh_loadCFwd / qPerGal; // attempt to meet entire carry-forward load
1875+ wh_loadCFwd= 0 .; // clear carry-forward, if not met, reset just below
1876+ if (drawForTick > 0 .)
1877+ { nTickNZDraw++;
1878+ if (drawForTick > drawFullLoad)
1879+ { nTickFullLoad += 1 .; // full capacity operation
1880+ wh_loadCFwd = qPerGal * (drawForTick - drawFullLoad); // unmet load
1881+ if (wh_loadCFwd > wh_loadCFwdMax)
1882+ { wh_HPWHxBU += wh_loadCFwd - wh_loadCFwdMax; // excess heating required to meet ws_tUse
1883+ wh_loadCFwd = wh_loadCFwdMax;
1884+ }
1885+ }
1886+ else
1887+ nTickFullLoad += drawForTick / drawFullLoad;
1888+ if (wh_stbyTicks) // if no draw in prior tick
1889+ { // cold start after 30 min, linearly reduced for short times
1890+ nColdStarts += min ( 1 ., wh_stbyTicks * Top.tp_subhrTickDur / 30 .);
1891+ wh_stbyTicks = 0 ;
1892+ }
1893+ }
1894+ else
1895+ // standby
1896+ wh_stbyTicks++; // count conseq ticks w/o draw
1897+ }
1898+
1899+ double tickDurHr = Top.tp_subhrTickDur / 60 .; // tick duration, hr
1900+ double rcovFuel = wh_maxInpX * nTickFullLoad;
1901+ double startFuel = wh_cycLossFuel * nColdStarts;
1902+ double rcovElec = wh_operElec * nTickNZDraw * tickDurHr; // assume operation for entire tick with any draw
1903+ // double startElec = wh_cycLossElec * nTickStart; // unused in revised model
1904+ // standby in ticks w/o draw
1905+ double stbyElec = wh_stbyElec * (Top.tp_nSubhrTicks -nTickNZDraw) * tickDurHr;
1906+ #else
17931907 double volxBU = 0.; // volume in excess of capacity, gal
17941908 int nTickNZDraw = 0; // count of ticks with draw > 0
17951909 double nTickFullLoad = 0.; // fractional ticks of equiv full load
@@ -1820,9 +1934,9 @@ RC DHWHEATER::wh_InstUEFDoSubhr(
18201934 // double startElec = wh_cycLossElec * nTickStart; // unused in revised model
18211935 // standby in ticks w/o draw
18221936 double stbyElec = wh_stbyElec * (Top.tp_nSubhrTicks-nTickNZDraw) * tickDurHr;
1823-
18241937 wh_HPWHxBU = 8.345 * volxBU * deltaT;
18251938 // excess heating required to meet ws_tUse
1939+ #endif
18261940
18271941 // energy use accounting, Btu
18281942 double inFuel = rcovFuel + startFuel;
0 commit comments