Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heat exchange models #183

Merged
merged 34 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
99efbed
Restart heat exchange model.
Nov 1, 2023
8618861
Add heat exchange params.
Nov 1, 2023
508b544
AquaThermAire fails file and regression tests.
Nov 1, 2023
6e78b76
Fails villara test.
Nov 1, 2023
4681954
Adjust villara time.
Nov 1, 2023
7a6b418
Aquatherm added.
Nov 2, 2023
ad7efa0
Merge branch 'master' into heat-exchange-models
spahrenk Nov 6, 2023
539ef53
Merge branch 'master' into heat-exchange-models
Nov 9, 2023
638a51e
Merge branch 'master' into heat-exchange-models
Nov 13, 2023
ccdc7d2
accept master
Nov 13, 2023
098874d
Remove specific AquaTerm refs.
Nov 13, 2023
9bb19c7
No AquaTherm refs.
Nov 13, 2023
2fa7021
AquaTherm back in.
Nov 13, 2023
3611502
Review changes; unlock condenser.
Nov 20, 2023
92d5edf
Merge branch 'master' into heat-exchange-models
Nov 20, 2023
19ae202
Merge branch 'master' into heat-exchange-models
Nov 29, 2023
63b453a
Use 800 C storage tank to pass solar test.
Dec 6, 2023
e7bb180
Merge master.
Dec 11, 2023
592b89e
Fix heating logic issues.
Dec 11, 2023
e2d006f
Set AquaTherm to 12 tank nodes.
Dec 11, 2023
b8e569c
Use whole-tank logic.
Dec 11, 2023
9348872
Merge branch 'master' into heat-exchange-models
Dec 11, 2023
5043494
Improve unit conversion.
Dec 12, 2023
0dcc923
Conform case.
Dec 12, 2023
7553fdd
Merge branch 'master' into heat-exchange-models
Dec 12, 2023
4f64c7a
Merge branch 'master' into heat-exchange-models
Dec 13, 2023
533ec62
Change heat-exch-eff coeff.
Dec 14, 2023
4de5ef1
Define node-heat-exch-effec.
Dec 14, 2023
c5e0c99
Check validity.
Dec 14, 2023
e3f4a7c
Add 2nd third function.
Dec 14, 2023
2e25a71
Review comments; remove outletT space.
Dec 15, 2023
4a3e3f9
Remove unused fnc.
Dec 18, 2023
31cec6c
Change coil config.
Dec 18, 2023
ad4f931
Merge branch 'heat-exchange-models' of https://github.com/bigladder/H…
Dec 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
190 changes: 136 additions & 54 deletions src/HPWH.cc
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ void HPWH::setAllDefaults() {
usesSoCLogic = false;
setMinutesPerStep(1.0);
hpwhVerbosity = VRB_minuteOut;
hasHeatExchanger = false;
heatExchangerEffectiveness = 0.9;
}

HPWH::HPWH(const HPWH &hpwh) {
Expand Down Expand Up @@ -938,7 +940,7 @@ int HPWH::WriteCSVHeading(FILE* outFILE,const char* preamble,int nTCouples,int o
fprintf(outFILE,",tcouple%d (%s)",iTC + 1,doIP ? "F" : "C");
}

fprintf(outFILE,", toutlet (%s)",doIP ? "F" : "C");
fprintf(outFILE,",toutlet (%s)",doIP ? "F" : "C");

fprintf(outFILE,"\n");

Expand Down Expand Up @@ -1738,14 +1740,26 @@ std::vector<HPWH::NodeWeight> HPWH::getNodeWeightRange(double bottomFraction, do
return nodeWeights;
}

std::shared_ptr<HPWH::TempBasedHeatingLogic> HPWH::wholeTank(double decisionPoint,const UNITS units /* = UNITS_C */, const bool absolute /* = false */) {
std::vector<NodeWeight> nodeWeights = getNodeWeightRange(0., 1.);
double decisionPoint_C = convertTempToC(decisionPoint,units,absolute);
return std::make_shared<HPWH::TempBasedHeatingLogic>("whole tank",nodeWeights,decisionPoint_C,this,absolute);
}

std::shared_ptr<HPWH::TempBasedHeatingLogic> HPWH::topThird(double decisionPoint) {
std::vector<NodeWeight> nodeWeights = getNodeWeightRange(2./3., 1.);
return std::make_shared<HPWH::TempBasedHeatingLogic>("top third",nodeWeights,decisionPoint,this);
}

std::shared_ptr<HPWH::TempBasedHeatingLogic> HPWH::topThird_absolute(double decisionPoint) {
std::vector<NodeWeight> nodeWeights = getNodeWeightRange(2./3., 1.);
return std::make_shared<HPWH::TempBasedHeatingLogic>("top third absolute",nodeWeights,decisionPoint,this);
return std::make_shared<HPWH::TempBasedHeatingLogic>("top third absolute",nodeWeights,decisionPoint,this,true);
}

std::shared_ptr<HPWH::TempBasedHeatingLogic> HPWH::secondThird(double decisionPoint,const UNITS units /* = UNITS_C */, const bool absolute /* = false */) {
std::vector<NodeWeight> nodeWeights = getNodeWeightRange(1./3., 2./3.);
double decisionPoint_C = convertTempToC(decisionPoint,units,absolute);
return std::make_shared<HPWH::TempBasedHeatingLogic>("second third",nodeWeights,decisionPoint_C,this,absolute);
}

std::shared_ptr<HPWH::TempBasedHeatingLogic> HPWH::bottomThird(double decisionPoint) {
Expand Down Expand Up @@ -2581,77 +2595,93 @@ void HPWH::updateTankTemps(double drawVolume_L,double inletT_C,double tankAmbien
lowInletT = inletT_C;
lowInletV = drawVolume_L - inletVol2_L;
}
//calculate how many nodes to draw (drawVolume_N)
double drawVolume_N = drawVolume_L / nodeVolume_L;
if(drawVolume_L > tankVolume_L) {
//if (hpwhVerbosity >= VRB_reluctant) {
// //msg("WARNING: Drawing more than the tank volume in one step is undefined behavior. Terminating simulation. \n");
// msg("WARNING: Drawing more than the tank volume in one step is undefined behavior. Continuing simulation at your own risk. \n");
//}
//simHasFailed = true;
//return;
for(int i = 0; i < getNumNodes(); i++){
outletTemp_C += tankTemps_C[i];
tankTemps_C[i] = (inletT_C * (drawVolume_L - inletVol2_L) + inletT2_C * inletVol2_L) / drawVolume_L;
}
outletTemp_C = (outletTemp_C / getNumNodes() * tankVolume_L + tankTemps_C[0] * (drawVolume_L - tankVolume_L))
/ drawVolume_L * drawVolume_N;

drawVolume_N = 0.;
if (hasHeatExchanger) {// heat-exchange models

const double densityTank_kgperL = DENSITYWATER_kgperL;
const double CpTank_kJperkgC = CPWATER_kJperkgC;

double C_Node_kJperC = CpTank_kJperkgC * densityTank_kgperL * nodeVolume_L;
double C_draw_kJperC = CPWATER_kJperkgC * DENSITYWATER_kgperL * drawVolume_L;

outletTemp_C = inletT_C;
for (auto &nodeTemp: tankTemps_C) {
double maxHeatExchange_kJ = C_draw_kJperC * (nodeTemp - outletTemp_C);
double heatExchange_kJ = nodeHeatExchangerEffectiveness * maxHeatExchange_kJ;

nodeTemp -= heatExchange_kJ / C_Node_kJperC;
outletTemp_C += heatExchange_kJ / C_draw_kJperC;
}
}
else {
//calculate how many nodes to draw (drawVolume_N)
double drawVolume_N = drawVolume_L / nodeVolume_L;
if(drawVolume_L > tankVolume_L) {
//if (hpwhVerbosity >= VRB_reluctant) {
// //msg("WARNING: Drawing more than the tank volume in one step is undefined behavior. Terminating simulation. \n");
// msg("WARNING: Drawing more than the tank volume in one step is undefined behavior. Continuing simulation at your own risk. \n");
//}
//simHasFailed = true;
//return;
for(int i = 0; i < getNumNodes(); i++){
outletTemp_C += tankTemps_C[i];
tankTemps_C[i] = (inletT_C * (drawVolume_L - inletVol2_L) + inletT2_C * inletVol2_L) / drawVolume_L;
}
outletTemp_C = (outletTemp_C / getNumNodes() * tankVolume_L + tankTemps_C[0] * (drawVolume_L - tankVolume_L))
/ drawVolume_L * drawVolume_N;

/////////////////////////////////////////////////////////////////////////////////////////////////
drawVolume_N = 0.;
}

while(drawVolume_N > 0) {

while(drawVolume_N > 0) {
// Draw one node at a time
double drawFraction = drawVolume_N > 1. ? 1. : drawVolume_N;
double nodeInletTV = 0.;

// Draw one node at a time
double drawFraction = drawVolume_N > 1. ? 1. : drawVolume_N;
double nodeInletTV = 0.;
//add temperature for outletT average
outletTemp_C += drawFraction * tankTemps_C[getNumNodes() - 1];

//add temperature for outletT average
outletTemp_C += drawFraction * tankTemps_C[getNumNodes() - 1];
double cumInletFraction = 0.;
for(int i = getNumNodes() - 1; i >= lowInletH; i--) {

double cumInletFraction = 0.;
for(int i = getNumNodes() - 1; i >= lowInletH; i--) {

// Reset inlet inputs at this node.
double nodeInletFraction = 0.;
nodeInletTV = 0.;

// Sum of all inlets Vi*Ti at this node
if(i == highInletH) {
nodeInletTV += highInletV * drawFraction / drawVolume_L * highInletT;
nodeInletFraction += highInletV * drawFraction / drawVolume_L;
nodeInletTV += highInletV * drawFraction / drawVolume_L * highInletT;
nodeInletFraction += highInletV * drawFraction / drawVolume_L;
}
if(i == lowInletH) {
nodeInletTV += lowInletV * drawFraction / drawVolume_L * lowInletT;
nodeInletFraction += lowInletV * drawFraction / drawVolume_L;
nodeInletTV += lowInletV * drawFraction / drawVolume_L * lowInletT;
nodeInletFraction += lowInletV * drawFraction / drawVolume_L;

break; // if this is the bottom inlet break out of the four loop and use the boundary condition equation.
}

// Look at the volume and temperature fluxes into this node
tankTemps_C[i] = (1. - (drawFraction - cumInletFraction)) * tankTemps_C[i] +
nodeInletTV +
(drawFraction - (cumInletFraction + nodeInletFraction)) * tankTemps_C[i - 1];

cumInletFraction += nodeInletFraction;
// Look at the volume and temperature fluxes into this node
tankTemps_C[i] = (1. - (drawFraction - cumInletFraction)) * tankTemps_C[i] +
nodeInletTV +
(drawFraction - (cumInletFraction + nodeInletFraction)) * tankTemps_C[i - 1];

}

// Boundary condition equation because it shouldn't take anything from tankTemps_C[i - 1] but it also might not exist.
tankTemps_C[lowInletH] = (1. - (drawFraction - cumInletFraction)) * tankTemps_C[lowInletH] + nodeInletTV;
cumInletFraction += nodeInletFraction;

drawVolume_N -= drawFraction;
}

mixTankInversions();
}
// Boundary condition equation because it shouldn't take anything from tankTemps_C[i - 1] but it also might not exist.
tankTemps_C[lowInletH] = (1. - (drawFraction - cumInletFraction)) * tankTemps_C[lowInletH] + nodeInletTV;

drawVolume_N -= drawFraction;

//fill in average outlet T - it is a weighted averaged, with weights == nodes drawn
this->outletTemp_C /= (drawVolume_L / nodeVolume_L);
mixTankInversions();
}

/////////////////////////////////////////////////////////////////////////////////////////////////
//fill in average outlet T - it is a weighted averaged, with weights == nodes drawn
outletTemp_C /= (drawVolume_L / nodeVolume_L);
}

//Account for mixing at the bottom of the tank
if(tankMixesOnDraw && drawVolume_L > 0.) {
Expand Down Expand Up @@ -3024,6 +3054,9 @@ void HPWH::calcSizeConstants() {

// fracAreaSide is the faction of the area of the cylinder that's not the top or bottom.
fracAreaSide = tankHeight_m / (tankHeight_m + tankRad_m);

/// Single-node heat-exchange effectiveness
nodeHeatExchangerEffectiveness = 1. - pow(1. - heatExchangerEffectiveness, 1./ static_cast<double>(getNumNodes()));
}

void HPWH::calcDerivedValues() {
Expand Down Expand Up @@ -3141,7 +3174,6 @@ void HPWH::mapResRelativePosToHeatSources() {
});
}


// Used to check a few inputs after the initialization of a tank model from a preset or a file.
int HPWH::checkInputs() {
int returnVal = 0;
Expand Down Expand Up @@ -3194,7 +3226,8 @@ int HPWH::checkInputs() {

//check is condensity sums to 1
condensitySum = 0;
for(int j = 0; j < heatSources[i].getCondensitySize(); ++j) condensitySum += heatSources[i].condensity[j];

for(int j = 0; j < heatSources[i].getCondensitySize(); j++) condensitySum += heatSources[i].condensity[j];
if(fabs(condensitySum - 1.0) > 1e-6) {
if(hpwhVerbosity >= VRB_reluctant) {
msg("The condensity for heatsource %d does not sum to 1. \n",i);
Expand Down Expand Up @@ -3328,6 +3361,14 @@ int HPWH::checkInputs() {
returnVal = HPWH_ABORT;
}

// Check single-node heat-exchange effectiveness validity
if( heatExchangerEffectiveness > 1.) {
if(hpwhVerbosity >= VRB_reluctant) {
msg("Heat-exchanger effectiveness cannot exceed 1.\n");
}
returnVal = HPWH_ABORT;
}

//if there's no failures, return 0
return returnVal;
}
Expand Down Expand Up @@ -3515,7 +3556,21 @@ int HPWH::HPWHinit_file(string configFile) {
}
initalTankT_C = tempDouble;
hasInitialTankTemp = true;

} else if(token == "hasHeatExchanger") {
// false of this model uses heat exchange
line_ss >> tempString;
if(tempString == "true") hasHeatExchanger = true;
else if(tempString == "false") hasHeatExchanger = false;
else {
if(hpwhVerbosity >= VRB_reluctant) {
msg("Improper value for %s\n",token.c_str());
}
return HPWH_ABORT;
}
} else if(token == "heatExchangerEffectiveness") {
// applies to heat-exchange models only
line_ss >> tempDouble;
heatExchangerEffectiveness = tempDouble;
} else if(token == "verbosity") {
line_ss >> token;
if(token == "silent") {
Expand Down Expand Up @@ -3668,16 +3723,43 @@ int HPWH::HPWHinit_file(string configFile) {
heatSources[heatsource].standbyLogic = std::make_shared<HPWH::TempBasedHeatingLogic>("standby logic",nodeWeights,tempDouble,this,absolute,compare);
}
} else if(token == "onlogic") {
line_ss >> tempDouble >> units;
if(units == "F") tempDouble = dF_TO_dC(tempDouble);
else if(units == "C"); //do nothing, lol
std::string nextToken;
line_ss >> nextToken;
bool absolute = (nextToken == "absolute");
if(absolute) {
std::string compareStr;
line_ss >> compareStr >> tempDouble >> units;
std::function<bool(double,double)> compare;
if(compareStr == "<") compare = std::less<double>();
else if(compareStr == ">") compare = std::greater<double>();
else {
if(hpwhVerbosity >= VRB_reluctant) {
msg("Improper comparison, \"%s\", for heat source %d %s. Should be \"<\" or \">\".\n",compareStr.c_str(),heatsource,token.c_str());
}
return HPWH_ABORT;
}
line_ss >> tempDouble;
}
else {
tempDouble = std::stod(nextToken);
}
line_ss >> units;
if(units == "F") {
if(absolute) {
tempDouble = F_TO_C(tempDouble);
} else {
tempDouble = dF_TO_dC(tempDouble);
}
} else if(units == "C"); //do nothing, lol
else {
if(hpwhVerbosity >= VRB_reluctant) {
msg("Incorrect units specification for %s from heatsource %d. \n",token.c_str(),heatsource);
}
return HPWH_ABORT;
}
if(tempString == "topThird") {
if(tempString == "wholeTank") {
heatSources[heatsource].addTurnOnLogic(HPWH::wholeTank(tempDouble,UNITS_C,absolute));
} else if(tempString == "topThird") {
heatSources[heatsource].addTurnOnLogic(HPWH::topThird(tempDouble));
} else if(tempString == "bottomThird") {
heatSources[heatsource].addTurnOnLogic(HPWH::bottomThird(tempDouble));
Expand Down
27 changes: 20 additions & 7 deletions src/HPWH.hh
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,9 @@ public:
MODELS_RHEEM_HPHD60HNU_201_MP = 350,
MODELS_RHEEM_HPHD60VNU_201_MP = 351,
MODELS_RHEEM_HPHD135HNU_483_MP = 352, // really bad fit to data due to inconsistency in data
MODELS_RHEEM_HPHD135VNU_483_MP = 353 // really bad fit to data due to inconsistency in data
MODELS_RHEEM_HPHD135VNU_483_MP = 353, // really bad fit to data due to inconsistency in data

MODELS_AquaThermAire = 400 // heat exchanger model
};

///specifies the modes for writing output
Expand Down Expand Up @@ -372,8 +374,10 @@ public:
std::shared_ptr<HPWH::SoCBasedHeatingLogic> turnOnSoC(std::string desc,double targetSoC,double hystFract,double tempMinUseful_C,
bool constMains,double mains_C);

std::shared_ptr<TempBasedHeatingLogic> wholeTank(double decisionPoint,const UNITS units = UNITS_C, const bool absolute = false);
std::shared_ptr<TempBasedHeatingLogic> topThird(double decisionPoint);
std::shared_ptr<TempBasedHeatingLogic> topThird_absolute(double decisionPoint);
std::shared_ptr<TempBasedHeatingLogic> secondThird(double decisionPoint,const UNITS units = UNITS_C, const bool absolute = false);
std::shared_ptr<TempBasedHeatingLogic> bottomThird(double decisionPoint);
std::shared_ptr<TempBasedHeatingLogic> bottomHalf(double decisionPoint) ;
std::shared_ptr<TempBasedHeatingLogic> bottomTwelfth(double decisionPoint);
Expand Down Expand Up @@ -708,11 +712,6 @@ public:
energy put into the water is positive - should always be positive
returns HPWH_ABORT for N out of bounds or incorrect units */

double getNthHeatSourceExtraEnergyInput(int N,UNITS units = UNITS_KWH) const;
/**< returns the "extra" energy input to the Nth heat source, with the specified units
energy used by the heat source is positive - should always be positive
returns HPWH_ABORT for N out of bounds or incorrect units */

double getNthHeatSourceRunTime(int N) const;
/**< returns the run time for the Nth heat source, in minutes
note: they may sum to more than 1 time step for concurrently running heat sources
Expand Down Expand Up @@ -939,7 +938,7 @@ private:
/**< the mass of water (kg) in a single node */
double nodeMass_kg;

/**< the heat capacity of the water (kJ/�C) in a single node */
/**< the heat capacity of the water (kJ/�C) in a single node */
double nodeCp_kJperC;

/**< the height in meters of the one node */
Expand Down Expand Up @@ -1026,6 +1025,15 @@ private:
/// Generates a vector of logical nodes
std::vector<HPWH::NodeWeight> getNodeWeightRange(double bottomFraction,double topFraction);

/// False: water is drawn from the tank itself; True: tank provides heat exchange only
bool hasHeatExchanger;

/// Coefficient (0-1) of effectiveness for heat exchange between tank and water line (used by heat-exchange models only).
double heatExchangerEffectiveness;

/// Coefficient (0-1) of effectiveness for heat exchange between a single tank node and water line (derived from heatExchangerEffectiveness).
double nodeHeatExchangerEffectiveness;

}; //end of HPWH class

class HPWH::HeatSource {
Expand Down Expand Up @@ -1350,6 +1358,11 @@ inline HPWH::DRMODES operator|(HPWH::DRMODES a,HPWH::DRMODES b)

template< typename T> inline bool aboutEqual(T a,T b) { return fabs(a - b) < HPWH::TOL_MINVALUE; }

/// Generate an absolute or relative temperature in degC.
inline double convertTempToC(const double T_F_or_C,const HPWH::UNITS units,const bool absolute){
return (units == HPWH::UNITS_C) ? T_F_or_C : (absolute ? F_TO_C(T_F_or_C) : dF_TO_dC(T_F_or_C));
}

// resampling utility functions
double getResampledValue(const std::vector<double> &values,double beginFraction,double endFraction);
bool resample(std::vector<double> &values,const std::vector<double> &sampleValues);
Expand Down