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

Better humidity control for chilled water coils and for AirloopHVAC:UnitarySystem #7215

Merged
merged 18 commits into from
Mar 20, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
cb6f108
Temporary fix in GetCoilDesFlowT to protect against array bounds viol…
mjwitte Mar 8, 2019
3adf350
UnitarySystem fix humidity setpoint controls
mjwitte Mar 8, 2019
865c9f0
Merge remote-tracking branch 'remotes/origin/develop' into ChwCoilHum…
mjwitte Mar 9, 2019
53543cb
ChW controller improve temperature and humidity control
mjwitte Mar 10, 2019
7683df8
ChW controller - delete now unused variables
mjwitte Mar 11, 2019
6c08857
Revised fix in GetCoilDesFlowT to protect against array bounds violat…
mjwitte Mar 11, 2019
119c5bc
ChW controller - cleanup and move humctrl check into HVACControllers
mjwitte Mar 11, 2019
9451557
ChW controller - remove unused FirstHVACIteration args
mjwitte Mar 11, 2019
d136c4c
SetActuatedBranchFlowRate - avoid array bounds errors - move early es…
mjwitte Mar 11, 2019
56330bd
GetCoilDesFlowT_NoPeak - new unit test and cleanup
mjwitte Mar 11, 2019
ee06f4e
HVACControllers_CheckTempAndHumRatCtrl unit test
mjwitte Mar 11, 2019
0bd833d
Merge remote-tracking branch 'remotes/origin/develop' into ChwCoilHum…
mjwitte Mar 12, 2019
3f5a7e2
UnitarySystemModel_WaterCoilSPControl_Latent unit test
mjwitte Mar 12, 2019
4471a4e
ChW controller - vary offset only if needed for humidity control
mjwitte Mar 12, 2019
c029acf
Merge remote-tracking branch 'remotes/origin/develop' into ChwCoilHum…
mjwitte Mar 18, 2019
7fda469
UnitarySystem humidity controls - set node conditions in DX calc func…
mjwitte Mar 18, 2019
7883673
UnitarySystem humidity controls - fix unit test
mjwitte Mar 18, 2019
040fa15
UnitarySystem humidity controls - fix another unit test
mjwitte Mar 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 18 additions & 70 deletions src/EnergyPlus/HVACControllers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1280,54 +1280,23 @@ namespace HVACControllers {
}

} else if (SELECT_CASE_var == iTemperatureAndHumidityRatio) { // 'TemperatureAndHumidityRatio'
ControllerProps(ControlNum).SensedValue = Node(SensedNode).Temp;
// Setpoint temp calculated once each HVAC time step to identify approach temp and whether or not humrat control is necessary
// WARNING: The scheme for computing the setpoint for the dual temperature and humidity ratio
// control strategy breaks down whenever the sensed node temperature is modified by
// a controller fired after the current one. Indeed the final sensed node temperature
// is likely to have changed in the meantime if the other controller is active,
// thereby invalidating the setpoint calculation for the other controller performed
// earlier on the air loop.
if (ControllerProps(ControlNum).HumRatCtrlOverride) {
// Humidity ratio control
ControllerProps(ControlNum).SensedValue = Node(SensedNode).HumRat;
} else {
// Temperature control
ControllerProps(ControlNum).SensedValue = Node(SensedNode).Temp;
}
if (!ControllerProps(ControlNum).IsSetPointDefinedFlag) {
// NOTE: For TEMPANDHUMRAT control the computed value ControllerProps(ControlNum)%SetPointValue
// depends on:
// - Node(SensedNode)%HumRatMax
// - Node(SensedNode)%Temp
// - Node(SensedNode)%HumRat
if ((Node(SensedNode).HumRatMax > 0) && (Node(SensedNode).HumRat > Node(SensedNode).HumRatMax)) {
// Setpoint can only be computed once per time step
// Check if outlet air humidity ratio is greater than the set point. If so, calculate new temperature based set point.
// See routine CalcSimpleController() for the sequence of operations.
// Calculate the approach temperature (difference between SA dry-bulb temp and SA dew point temp)
ApproachTemp = Node(SensedNode).Temp - PsyTdpFnWPb(Node(SensedNode).HumRat, OutBaroPress);
// Calculate the dew point temperature at the SA humidity ratio setpoint
DesiredDewPoint = PsyTdpFnWPb(Node(SensedNode).HumRatMax, OutBaroPress);
// Adjust the calculated dew point temperature by the approach temp. Should be within 0.3C of air temperature.
HumidityControlTempSetPoint = DesiredDewPoint + min(0.3, ApproachTemp);
// NOTE: The next line introduces a potential discontinuity into the residual function
// which could prevent the root finder from finding the root it if were done at each
// controller iteration. For this reason we perform the setpoint calculation only
// once at the beginning of the controller and air loop simulation.
// Use lower of temperature and humidity based set point.
// See routine CalcSimpleController() for the sequence of operations.
ControllerProps(ControlNum).SetPointValue = min(
Node(SensedNode).TempSetPoint,
HumidityControlTempSetPoint); // Pure temperature setpoint | Temperature setpoint to achieve the humidity ratio setpoint
// Don't allow set point temperature to be below the actuator node water temperature
ControllerProps(ControlNum).SetPointValue =
max(ControllerProps(ControlNum).SetPointValue, Node(ControllerProps(ControlNum).ActuatedNode).Temp);
// Overwrite the "pure" temperature setpoint with the actual setpoint that takes into
// account the humidity ratio setpoint.
// NOTE: Check that this does not create side-effects somewhere else in the code.
Node(SensedNode).TempSetPoint = ControllerProps(ControlNum).SetPointValue;
// Finally indicate thate the setpoint has been computed
ControllerProps(ControlNum).IsSetPointDefinedFlag = true;
if (ControllerProps(ControlNum).HumRatCtrlOverride) {
// Humidity ratio control
ControllerProps(ControlNum).SetPointValue = Node(SensedNode).HumRatMax;
} else {
// Pure temperature setpoint control strategy
ControllerProps(ControlNum).SetPointValue = Node(SensedNode).TempSetPoint;
// Finally indicate thate the setpoint has been computed
ControllerProps(ControlNum).IsSetPointDefinedFlag = true;
}
// Finally indicate thate the setpoint has been computed
ControllerProps(ControlNum).IsSetPointDefinedFlag = true;
}

} else if (SELECT_CASE_var == iHumidityRatio) { // 'HumidityRatio'
Expand Down Expand Up @@ -1465,6 +1434,10 @@ namespace HVACControllers {
(0.001 / (2100.0 * max(ControllerProps(ControlNum).MaxVolFlowActuated, SmallWaterVolFlow))) * (HVACEnergyToler / 10.0);
// do not let the controller tolerance exceed 1/10 of the loop temperature tolerance.
ControllerProps(ControlNum).Offset = min(0.1 * HVACTemperatureToler, ControllerProps(ControlNum).Offset);
if (ControllerProps(ControlNum).ControlVar == HVACControllers::iTemperatureAndHumidityRatio) {
// for temperature and humidity control, need tighter tolerance if humidity control kicks in
ControllerProps(ControlNum).Offset = ControllerProps(ControlNum).Offset*0.1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to be able to trigger a tighter tolerance only when HumRatCtrlOverride = true.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that first but it didn't work. There's some initial setup step that I couldn't find that includes the offset.

}
ReportSizingOutput(ControllerProps(ControlNum).ControllerType,
ControllerProps(ControlNum).ControllerName,
"Controller Convergence Tolerance",
Expand Down Expand Up @@ -1587,33 +1560,8 @@ namespace HVACControllers {
FindRootSimpleController(ControlNum, FirstHVACIteration, IsConvergedFlag, IsUpToDateFlag, ControllerName);

} else {

// We need to evaluate the sensed node temperature with the max actuated value before
// we can compute the actual setpoint for the dual humidity ratio / temperature strategy.
{
auto const SELECT_CASE_var(ControllerProps(ControlNum).ControlVar);
if ((SELECT_CASE_var == iTemperature) || (SELECT_CASE_var == iHumidityRatio) || (SELECT_CASE_var == iFlow)) {
// Always start with min point by default for the other control strategies
ControllerProps(ControlNum).NextActuatedValue = RootFinders(ControlNum).MinPoint.X;

} else if (SELECT_CASE_var == iTemperatureAndHumidityRatio) {
if (!ControllerProps(ControlNum).IsSetPointDefinedFlag) {
// Always start with max point if setpoint not yet computed. See routine InitController().
ControllerProps(ControlNum).NextActuatedValue = RootFinders(ControlNum).MaxPoint.X;
} else {
// If setpoint already exists (i.e., HumRatMax <= 0) then try min point first as in simple
// temperature control case.
ControllerProps(ControlNum).NextActuatedValue = RootFinders(ControlNum).MinPoint.X;
}

} else {
// Should never happen
ShowSevereError("CalcSimpleController: HVAC controller failed at " + CreateHVACStepFullString());
ShowContinueError(" Controller name=" + ControllerProps(ControlNum).ControllerName);
ShowContinueError(" Unrecognized control variable type=" + TrimSigDigits(ControllerProps(ControlNum).ControlVar));
ShowFatalError("Preceding error causes program termination.");
}
}
// Always start with min point by default
ControllerProps(ControlNum).NextActuatedValue = RootFinders(ControlNum).MinPoint.X;
}

// Process current iterate and compute next candidate if needed
Expand Down
4 changes: 3 additions & 1 deletion src/EnergyPlus/HVACControllers.hh
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ namespace HVACControllers {
bool BypassControllerCalc; // set true for OA sys water coils
int AirLoopControllerIndex; // index to controller on specific air loop

bool HumRatCtrlOverride; // true if TemperatureAndHumidityRatio control switches to humidity ratio control

// Default Constructor
ControllerPropsType()
: ControllerType_Num(ControllerSimple_Type), ControlVar(iNoControlVariable), ActuatorVar(0), Action(iNoAction), InitFirstPass(true),
Expand All @@ -242,7 +244,7 @@ namespace HVACControllers {
ActuatedNodePlantLoopNum(0), ActuatedNodePlantLoopSide(0), ActuatedNodePlantLoopBranchNum(0), SensedNode(0),
IsSetPointDefinedFlag(false), SetPointValue(0.0), SensedValue(0.0), DeltaSensed(0.0), Offset(0.0), HumRatCntrlType(0), Range(0.0),
Limit(0.0), TraceFileUnit(0), FirstTraceFlag(true), BadActionErrCount(0), BadActionErrIndex(0), FaultyCoilSATFlag(false),
FaultyCoilSATIndex(0), FaultyCoilSATOffset(0.0), BypassControllerCalc(false), AirLoopControllerIndex(0)
FaultyCoilSATIndex(0), FaultyCoilSATOffset(0.0), BypassControllerCalc(false), AirLoopControllerIndex(0), HumRatCtrlOverride(false)
{
}
};
Expand Down
25 changes: 15 additions & 10 deletions src/EnergyPlus/ReportSizingManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4441,7 +4441,7 @@ namespace ReportSizingManager {
int PeakLoadType;
int DDAtTotPeak;
int TimeStepAtTotPeak;
int TimeStepAtPeak;
int TimeStepAtPeak(0);
Real64 ZoneCoolLoadSum(0); // sum of zone cooling loads at the peak [W]
Real64 AvgZoneTemp(0); // average zone temperature [C]
Real64 AvgSupTemp; // average supply temperature for bypass control [C]
Expand All @@ -4451,23 +4451,28 @@ namespace ReportSizingManager {
CoolCapCtrl = SysSizInput(SysNum).CoolCapControl;
PeakLoadType = SysSizInput(SysNum).CoolingPeakLoadType;
DDAtSensPeak = SysSizPeakDDNum(SysNum).SensCoolPeakDD;
TimeStepAtSensPeak = SysSizPeakDDNum(SysNum).TimeStepAtSensCoolPk(DDAtSensPeak);
DDAtFlowPeak = SysSizPeakDDNum(SysNum).CoolFlowPeakDD;
TimeStepAtFlowPeak = SysSizPeakDDNum(SysNum).TimeStepAtCoolFlowPk(DDAtFlowPeak);
DDAtTotPeak = SysSizPeakDDNum(SysNum).TotCoolPeakDD;
TimeStepAtTotPeak = SysSizPeakDDNum(SysNum).TimeStepAtTotCoolPk(DDAtTotPeak);
if (DDAtSensPeak > 0) {
TimeStepAtSensPeak = SysSizPeakDDNum(SysNum).TimeStepAtSensCoolPk(DDAtSensPeak);
DDAtFlowPeak = SysSizPeakDDNum(SysNum).CoolFlowPeakDD;
TimeStepAtFlowPeak = SysSizPeakDDNum(SysNum).TimeStepAtCoolFlowPk(DDAtFlowPeak);
DDAtTotPeak = SysSizPeakDDNum(SysNum).TotCoolPeakDD;
TimeStepAtTotPeak = SysSizPeakDDNum(SysNum).TimeStepAtTotCoolPk(DDAtTotPeak);

if (PeakLoadType == TotalCoolingLoad) {
TimeStepAtPeak = TimeStepAtTotPeak;
} else {
TimeStepAtPeak = TimeStepAtSensPeak;
if (PeakLoadType == TotalCoolingLoad) {
TimeStepAtPeak = TimeStepAtTotPeak;
} else {
TimeStepAtPeak = TimeStepAtSensPeak;
}
}
if (CoolCapCtrl == VAV) {
DesExitTemp = FinalSysSizing(SysNum).CoolSupTemp;
DesFlow = FinalSysSizing(SysNum).MassFlowAtCoolPeak / StdRhoAir;
} else if (CoolCapCtrl == OnOff) {
DesExitTemp = FinalSysSizing(SysNum).CoolSupTemp;
DesFlow = DataAirFlowUsedForSizing;
} else if (TimeStepAtPeak == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TimeStepAtPeak can't be 0 if VAV or OnOff fan control is used? If so then this line won't get hit. This IF test should be first in list?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see. You put this IF test before those that use TimeStepAtPeak.

ShowFatalError("GetCoilDesFlow: AirLoopHVAC=" + SysSizInput(SysNum).AirPriLoopName +
" Central Cooling Capacity Control Method requires zone thermostats to calculate timestep loads.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can still have a no design load condition (and therefore a 0 for time step at peak) even though a Tstat is used. Remember when zone return temp at heating peak was 0 and TU coils weren't sizing correctly? It's the combination of Tstat temp (i.e., zone temp during sizing) and supply air temp that yields a non-zero zone mass flow rate. Isn't there another way to know if a Tstat is used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, even with a tstat, there could be zero peak. So, should these methods throw a warning if there's no peak day/time and revert to one of the other methods?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe change the warning text to say exactly what has happened (inside the else if's below) and revert to sizing with CoolSupTemp and MassFlowAtCoolPeak?

} else if (CoolCapCtrl == VT) {
if (FinalSysSizing(SysNum).CoolingPeakLoadType == SensibleCoolingLoad) {
ZoneCoolLoadSum = CalcSysSizing(SysNum).SumZoneCoolLoadSeq(TimeStepAtPeak);
Expand Down
28 changes: 28 additions & 0 deletions src/EnergyPlus/SimAirServingZones.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2864,6 +2864,9 @@ namespace SimAirServingZones {
// E.g., actuator inlet water flow
for (int AirLoopControlNum = 1; AirLoopControlNum <= PrimaryAirSystem(AirLoopNum).NumControllers; ++AirLoopControlNum) {

// For temperature and humidity control reset humidity control override
HVACControllers::ControllerProps(PrimaryAirSystem(AirLoopNum).ControllerIndex(AirLoopControlNum)).HumRatCtrlOverride = false;

// BypassOAController is true here since we do not want to simulate the controller if it has already been simulated in the OA system
// ControllerConvergedFlag is returned true here for water coils in OA system
ManageControllers(PrimaryAirSystem(AirLoopNum).ControllerName(AirLoopControlNum),
Expand Down Expand Up @@ -2960,6 +2963,31 @@ namespace SimAirServingZones {
// not converge
ControllerConvergedFlag = PrimaryAirSystem(AirLoopNum).ControlConverged(AirLoopControlNum);
IsUpToDateFlag = true;
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will require multiple more iterations of the air loop components. Wouldn't it be better to use the lesser of T and TdpFw as the temperature set point up at line 1283 - 1289 of HVACControllers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can try that on a separate branch, but I think it will over-dehumidify.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or use T and TdpFw as a trigger to choose which set point to use, temperature or humidity ratio.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, don't need to try that. The humidity ratio setpoint is 0.011388 which is roughly Tdp=15.9C. There are many hours where the coil can meet the humidity ratio setpoint with a leaving temperature of 16-17C, and there are many hours where the leaving air is saturated. That's why this needs to switch gears and can't just use Tdp.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, how can Tdp come out lower than supply air T? If a dry coil then yes, but a wet coil? Not your problem at this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'cause the water temp is well below Tdp, and some of the air bypasses the coil, so the resulting mix can be drier than saturation.

{
auto &thisController(HVACControllers::ControllerProps(PrimaryAirSystem(AirLoopNum).ControllerIndex(AirLoopControlNum)));
if (thisController.ControlVar == HVACControllers::iTemperatureAndHumidityRatio) {
// For temperature and humidity control, after temperature control is converged, check if humidity setpoint is met
if (!thisController.HumRatCtrlOverride) {
if (Node(thisController.SensedNode).HumRat > (Node(thisController.SensedNode).HumRatMax + thisController.Offset)) {
// Turn on humdity control and restart controller
ControllerConvergedFlag = false;
thisController.HumRatCtrlOverride = true;
IsUpToDateFlag = false;
ManageControllers(PrimaryAirSystem(AirLoopNum).ControllerName(AirLoopControlNum),
PrimaryAirSystem(AirLoopNum).ControllerIndex(AirLoopControlNum),
FirstHVACIteration,
AirLoopNum,
iControllerOpWarmRestart,
ControllerConvergedFlag,
IsUpToDateFlag,
BypassOAController,
AllowWarmRestartFlag);
SimAirLoopComponents(AirLoopNum, FirstHVACIteration);
}
}
}
}
}

} // End of the Convergence Iteration
Expand Down
8 changes: 6 additions & 2 deletions src/EnergyPlus/UnitarySystem.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10406,7 +10406,7 @@ namespace UnitarySystems {
// and if coolReheat, check hum rat as well
if (((NoLoadTempOut - DesOutTemp) < Acc) && ((NoLoadHumRatOut - DesOutHumRat) < HumRatAcc)) {
PartLoadFrac = 0.0;
} else if (SensibleLoad) { // need to turn on compressor to see if load is met
} else { // need to turn on compressor to see if load is met
PartLoadFrac = 1.0;
CompOn = 1;
m_WSHPRuntimeFrac = 1.0;
Expand Down Expand Up @@ -10571,7 +10571,9 @@ namespace UnitarySystems {
(this->m_TESOpMode == PackagedThermalStorageCoil::OffMode ||
this->m_TESOpMode == PackagedThermalStorageCoil::ChargeOnlyMode)) {
PartLoadFrac = 0.0;
} else {
} else if (!SensibleLoad) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Highlights an issue with all coils types when no sensible load exists and a latent load does. Logic at top of function could skip sensible PLR calcs and go directly to latent calcs. Sensible calcs are inside of if(SensibleLoad || LatentLoad) block.

PartLoadFrac = 0.0;
} else if (SensibleLoad) {

Par[9] = double(AirLoopNum);
Par[10] = 0.0;
Expand Down Expand Up @@ -10866,6 +10868,7 @@ namespace UnitarySystems {
FullOutput = DataLoopNode::Node(InletNode).MassFlowRate *
(Psychrometrics::PsyHFnTdbW(DataLoopNode::Node(OutletNode).Temp, DataLoopNode::Node(OutletNode).HumRat) -
Psychrometrics::PsyHFnTdbW(DataLoopNode::Node(InletNode).Temp, DataLoopNode::Node(OutletNode).HumRat));
FullLoadHumRatOut = DataLoopNode::Node(OutletNode).HumRat;

// Check to see if the system can meet the load with the compressor off
// If NoOutput is lower than (more cooling than required) or very near the ReqOutput, do not run the compressor
Expand Down Expand Up @@ -10906,6 +10909,7 @@ namespace UnitarySystems {
FullOutput = DataLoopNode::Node(InletNode).MassFlowRate *
(Psychrometrics::PsyHFnTdbW(DataLoopNode::Node(OutletNode).Temp, DataLoopNode::Node(InletNode).HumRat) -
Psychrometrics::PsyHFnTdbW(DataLoopNode::Node(InletNode).Temp, DataLoopNode::Node(InletNode).HumRat));
FullLoadHumRatOut = DataLoopNode::Node(OutletNode).HumRat;

// Since we are cooling, we expect FullOutput to be < 0 and FullOutput < NoCoolOutput
// Check that this is the case; IF not set PartLoadFrac = 0.0 (off) and return
Expand Down