-
Notifications
You must be signed in to change notification settings - Fork 418
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
Model DOAS Supplying Air to Inlets of Multiple AHUs #7230
Model DOAS Supplying Air to Inlets of Multiple AHUs #7230
Conversation
design/FY2019/NFP-DOAS_MultiAHUs.md
Outdated
|
||
####New objects | ||
|
||
The proposed new objects are AirLoopHVAC:DedicatedOutdoorAirSystem, AirLoopHVAC:DedicatedOutdoorAirSystem:Mixer, and AirLoopHVAC:DedicatedOutdoorAirSystem:Splitter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Zone equipment already allow DOAS connected to inlets. It would be better for GUIs if the same object was used to connect to air loop equipment. The object is AirTerminal:SingleDuct:Mixer/Splitter. There are existing functions that calculate the mixing and other functions used for component sizing. The functions are in SingleDuct.cc and at the bottom of ReportSizingManager. The existing functions in SingleDuct.cc could be used by all equipment models. Also, we already have AirloopHVAC that can act as DOAS and don't need another dedicated object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rraustad Thanks for your comment with quick feedback. The proposed new feature will have a central DOAS to serve multiple AirLoops. The name of AirTerminal:* may not be appropriated. I think any names with AirLoop are better. That is what is proposed. In addition, the proposed feature is a high level management to provide enough inputs to map required configurations. The detailed calculations will use existing functions. For example, any OA calculations will call existing OA controllers.
The proposed object is similar with the AirLoop object with two main differences. The first difference is that the proposed object requires two nodes: inlet and outlet only, while the AirLoopHVAC object requires two pairs of node based supply and demand sides. In addition, the proposed object requires more inputs, such as setpoint manager, AirLoopHVAC:DedicatedOutdoorAirSystem:Mixer, and AirLoopHVAC:DedicatedOutdoorAirSystem:Splitter.
The second difference is that AirLoopHVAC is used to serve multiple zones, while the proposed object is used to serve multiple AirLoops. Since difference purposes are specified, it should use different names to specify how to use these objects properly. In addition, we have issues with zone mass balance in an AirLoopHVAC. The proposed object deals with multiple AirLoops only and there is no potential mass balance issue. That is why I propose a new object name for a central DOAS system.
I was reviewing this offline while flying, so my apologies that these notes are not more integrated with the changes themselves. I can refine if needed:
|
@Myoldmopar Here are my answers to your concerns.
If successful, I will try to eliminate iteration to see what happens. If working, hope no iteration (this is what I mean). If not, use iteration approach already implemented in the beginning.
I am not aware of no change of IDD. Thanks for letting me know this restriction in the beginning. Do we allow extensible fields? If Yes, could you let me know how to do it. If not, I will use 500 node name, similar configuration of AirLoopHVAC:ZoneMixer. Please advise. |
@lgu1234 it has been 9 days since this pull request was last updated. |
@lgu1234 it has been 8 days since this pull request was last updated. |
@lgu1234 it has been 13 days since this pull request was last updated. |
@lgu1234 it has been 7 days since this pull request was last updated. |
@lgu1234 it has been 8 days since this pull request was last updated. |
@lgu1234 it has been 7 days since this pull request was last updated. |
@lgu1234 it has been 12 days since this pull request was last updated. |
@lgu1234 it has been 7 days since this pull request was last updated. |
@lgu1234 it has been 7 days since this pull request was last updated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lgu1234 I've suggested some code to allow the branch objects to be eliminated.
But it turns out that SetUpCompSets needs some extra lines as well - it assumes that child objects all know what their inlet and outlet node names are, but AirloopHVAC:OutdoorAirSystems does not know it's node names. So, I understand now, by adding the branch object, you had to specify the inlet and outlet node names for the AirllopHVAC:OtudoorAirSystem.
Here are the code changes to SetUpCompSets:
Or if you want, I can push these changes up to your branch. Let me know.
|
||
thisDOAS.m_InletNodeNum = OutsideAirSys(thisDOAS.m_OASystemNum).InletNodeNum(1); | ||
thisDOAS.m_OutletNodeNum = OutsideAirSys(thisDOAS.m_OASystemNum).OutletNodeNum(OutsideAirSys(thisDOAS.m_OASystemNum).NumComponents); | ||
OutsideAirSys(thisDOAS.m_OASystemNum).AirLoopDOASNum = AirLoopDOASNum - 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lgu1234 If you add this code here, then you should be able to delete the DOAS BranchList and Branch objects:
// Set up parent-child connection
BranchNodeConnections::SetUpCompSets(cCurrentModuleObject,
thisDOAS.Name,
"AIRLOOPHVAC:OUTDOORAIRSYSTEM",
thisDOAS.OASystemName,
DataLoopNode::NodeID(thisDOAS.m_InletNodeNum),
DataLoopNode::NodeID(thisDOAS.m_OutletNodeNum));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mjwitte I tested it. It worked. Thanks.
src/EnergyPlus/AirLoopHVACDOAS.cc
Outdated
for (auto instance = instancesValue.begin(); instance != instancesValue.end(); ++instance) { | ||
|
||
// *************** used only to eliminate unused object warning when using only Json type getInput ********** | ||
int TotalArgs = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getObjectItem
isn't necessary here. You can use inputProcessor->markObjectAsUsed(cCurrentModuleObject, thisObjectName);
to avoid unused warning. See WaterToWaterHeatPumpEIR.cc.
Of course, you'll have to change over the use of cAlphaFields
and Alphas
to use the json equivalents. Some for the other new objects in this feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not know the method yet to get down to this level and grab the field name.
"source_side_outlet_node_name": {
"field_type": "a",
"field_name": "Source Side Outlet Node Name"
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Seems there must be a better way than hard coding. @mbadams5 Is there a method to access field names for use in error messages?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also needed for #7353.
src/EnergyPlus/AirLoopHVACDOAS.cc
Outdated
if (errorsFound) { | ||
ShowContinueError("Outlet node number is not found in " + CurrentModuleObject + " = " + | ||
OutsideAirSys(thisDOAS.m_OASystemNum).ComponentName(CompNum)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These error messages are very repetitive. Could just store two error states and put a single instance of these after the if else if block.
If I am following this correctly, if one error occurs in any component (or if errorsFound has been set true before this loop) then every component will trigger Node number not found message, because errorsFound will stay true until the fatal error is called at line 1050.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mjwitte Let me think how to make it.
! ***GENERAL SIMULATION PARAMETERS*** | ||
! Number of Zones: 6 | ||
|
||
Version,9.1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember to move this to v9.2 and do any transitions required.
Also, this generates this error:
** Severe ** getFanObjectVectorIndex: did not find Fan:SystemModel name =PSZ-AC:1_FAN. Check inputs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mjwitte No transition is needed.
The version number has been changed in example file.
The new error is fixed.
@Myoldmopar I tried to add a default argument in
Unfortunately, Visual Studio doe not accept, although default argument is accepted in C++. The change I made is that I allow two arguments
Then I revised this function in 22 modules by adding one more argument. |
@lgu1234 For future reference, there is (at least) one example of a default argument in the current code in Psychrometrics.hh
Not sure why yours didn't work. Maybe it has to be &? But what you've done here is good. |
@mjwitte Thanks for letting me know. I also searched the web at https://www.geeksforgeeks.org/default-arguments-c/ as Default Arguments in C++ The code is valid in C++ as below: // A function with default arguments, it can be called with /* Driver program to test above function*/ When I added a default argument, HVACFan is OK for compiling. However, I got compile error to compile a whole project: Does not take 1 argument for this function. My guess is not a problem for C++ code. It may have some rules or restriction with HVACFan class (my guess). |
@lgu1234 When using the default argument, are you putting it in the header file (.hh)? The defaulted argument needs to be only in the header file and you need to use a non-defaulted argument in the implementation file (.cc). // In header file
int getFanObjectVectorIndex( // lookup vector index for fan object name in object array EnergyPlus::HVACFan::fanObjs
std::string const &objectName, // IDF name in input
bool const ErrorCheck = true); // In implementation file
int getFanObjectVectorIndex( // lookup vector index for fan object name in object array EnergyPlus::HVACFan::fanObjs
std::string const &objectName, // IDF name in input
bool const ErrorCheck)
{
// code
} If you did this and it still does not work then that is surprising. |
@mbadams5 Thanks. I am going to try and let you know what happens. |
@mbadams5 It worked. I am going to commit it with a default argument. Thanks. |
Solved conflicts a while ago. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ERR cleanups are nice. Overall it's good, but there are a couple things that I think need to be tidied, and aren't big efforts. There is a for
loop that uses a pointer reference, and that needs to be cleaned up - I've offered two possible solutions. There is also an if
statement that has a trailing indented line without brackets, and that can be visually deceiving and difficult to debug if someone doesn't recognize that it isn't a block. The unit test is massive, which is disappointing for a new feature, but I won't let that hold up the merge. I'll need to do a little testing on this branch, but in the meantime, please address my minor changes and make sure it's up to date with develop to get "final" CI results.
idd/Energy+.idd.in
Outdated
@@ -65030,6 +65030,346 @@ AirLoopHVAC:ReturnPath, | |||
\type object-list | |||
\object-list ReturnPathComponentNames | |||
|
|||
AirLoopHVAC:DedicatedOutdoorAirSystem, | |||
\extensible:1 Just duplicate last field and comments (changing numbering, please) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm tempted to ask you to take out the (change numbering, please)
comment, because changing the IDD is not an option for users, only developers. But it's OK, this can stay in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Myoldmopar It is not clear how to change numbering. I just following other objects. Please give me an example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's OK. Just leave it.
} | ||
|
||
std::string name; | ||
static AirLoopMixer *factory(int object_type_of_num, std::string const objectName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like where this is going already. 👍
AirLoopControlInfo(AirLoopNum).ReqstEconoLockoutWithCompressor = true; | ||
} else { | ||
AirLoopControlInfo(AirLoopNum).ReqstEconoLockoutWithCompressor = false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like AirLoopNum == -1
means one thing, AirLoopNum == 0
means another thing, and then AirLoopNum >= 1
means yet another thing. This is a bit complex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Myoldmopar AirLoopNum == -1 means the function of SimDXCoolingSystem is called from SimOutdoorAirEquipComps at https://github.com/NREL/EnergyPlus/blob/Model-DOAS-to-Multiple-Air-Handling-Units/src/EnergyPlus/OutdoorAirUnit.cc#L2396. When AirLoopNum == 0, this module is called from AirLoopHVACDOAS. When AirLoopNum > 0, this module is called by both MixedAir and SimAirServingZones.
Therefore, I need to keep it. In order to make it clear, I am going to add comments for both options.
} | ||
|
||
return NodeNum; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the future, you could simplify this up a bit. An acceptable function could be:
int GetAirOutletNodeNum(std::string const &HumidifierName,
bool &ErrorsFound
)
{
// PURPOSE OF THIS FUNCTION:
// This function looks up the given humidifier and returns the air outlet node number.
// If incorrect humidifier name is given, ErrorsFound is returned as true and node number as zero.
if (GetInputFlag) {
GetHumidifierInput();
GetInputFlag = false;
}
int WhichHumidifier = UtilityRoutines::FindItemInList(HumidifierName, Humidifier);
if (WhichHumidifier != 0) {
return Humidifier(WhichHumidifier).AirOutNode;
}
else {
ShowSevereError("GetAirInletNodeNum: Could not find Humidifier = \"" + HumidifierName +"\"");
ErrorsFound = true;
return 0;
}
}
Note that it eliminates the local variables, and that you don't need as much comment markup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Myoldmopar Done. Thanks.
src/EnergyPlus/MixedAir.cc
Outdated
@@ -1070,7 +1083,9 @@ namespace MixedAir { | |||
cNumericFields); | |||
UtilityRoutines::IsNameEmpty(AlphArray(1), CurrentModuleObject, ErrorsFound); | |||
OutsideAirSys(OASysNum).Name = AlphArray(1); | |||
GlobalNames::IntraObjUniquenessCheck(AlphArray(2), CurrentModuleObject, cAlphaFields(2), ControllerListUniqueNames, ErrorsFound); | |||
if (!UtilityRoutines::SameString(AlphArray(2), "")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just use:
if (!AlphArray(2).empty()) {
to check if a string is blank.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Myoldmopar Done. Thanks.
@@ -2619,6 +2642,8 @@ namespace MixedAir { | |||
// if ( BeginDayFlag ) { | |||
// } | |||
|
|||
if (OutsideAirSys(OASysNum).AirLoopDOASNum > -1) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a lot of these loop number flags floating around...I'm not able to track them fully at this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Myoldmopar I know. The main purpose is for by-pass, when AirLoopDOAS is used. In order to reuse existing code, I need to use by-passes to meet AirLoopDOAS requirements.
} | ||
thisAirLoopDOASObjec->SimAirLoopHVACDOAS(FirstHVACIteration, index); | ||
OAMassFLowrate += thisAirLoopDOASObjec->SumMassFlowRate; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this whole loop could use range based looping:
for (auto & thisAirLoopDOASObjec : airloopDOAS) { // <- let compiler create the reference and do the looping
if (thisAirLoopDOASObjec.m_AirLoopDOASNum > -1) {
index = thisAirLoopDOASObjec.m_AirLoopDOASNum;
} else {
index = -1;
}
thisAirLoopDOASObjec.SimAirLoopHVACDOAS(FirstHVACIteration, index);
OAMassFLowrate += thisAirLoopDOASObjec.SumMassFlowRate;
}
There's no need to introduce a pointer reference here. Even if you don't change the entire loop to a range based loop, it would be good to use a regular reference:
for (std::size_t loop = 0; loop < airloopDOAS.size(); ++loop) {
auto &thisAirLoopDOASObjec = airloopDOAS[loop]; // <- regular reference variable, not a pointer
if (thisAirLoopDOASObjec.m_AirLoopDOASNum > -1) {
index = thisAirLoopDOASObjec.m_AirLoopDOASNum;
} else {
index = -1;
}
thisAirLoopDOASObjec.SimAirLoopHVACDOAS(FirstHVACIteration, index);
OAMassFLowrate += thisAirLoopDOASObjec.SumMassFlowRate;
}
I don't want to introduce this pattern into the code, so this will be the first thing in my review that I would actually like to see get addressed. There may be other things as I go through, but this is the first thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Myoldmopar I am going to use a regular reference, so that the code change is less.
src/EnergyPlus/SimAirServingZones.cc
Outdated
|
||
AllowWarmRestartFlag = true; | ||
AirLoopControlInfo(AirLoopNum).AllowWarmRestartFlag = true; | ||
if (AirLoopNum > 0) AirLoopControlInfo(AirLoopNum).AllowWarmRestartFlag = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra logic checks are a waste of computation. Could this AirLoopNum be checked once and then a series of things be done only for that condition?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Myoldmopar Done. Thanks.
@@ -3097,7 +3188,8 @@ namespace SimAirServingZones { | |||
AllowWarmRestartFlag); | |||
|
|||
// Detect whether the speculative warm restart feature is supported by each controller on this air loop. | |||
AirLoopControlInfo(AirLoopNum).AllowWarmRestartFlag = AirLoopControlInfo(AirLoopNum).AllowWarmRestartFlag && AllowWarmRestartFlag; | |||
if (AirLoopNum > 0) | |||
AirLoopControlInfo(AirLoopNum).AllowWarmRestartFlag = AirLoopControlInfo(AirLoopNum).AllowWarmRestartFlag && AllowWarmRestartFlag; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code works fine, but it can cause visual indentation issues. It looks like you've started a new block since the statement portion of the IF statement is indented on the next line. But it's actually not. I would prefer to see brackets used here to avoid confusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Myoldmopar Done. Thanks.
|
||
namespace EnergyPlus { | ||
|
||
TEST_F(EnergyPlusFixture, AirLoopHVACDOASTest) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost 5000 lines for a single unit test. That's disappointing. I know writing unit tests for existing code can be difficult because of the interactions, but I always hope that for new features, it will be much more streamlined, since we know ahead of time we need to write unit tests for it, and things can be abstracted out to be nicely testable. Oh well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Myoldmopar I shrink the unit test with < 4000 line. However, I do need a lot of inputs to build the case for the unit test.
@Myoldmopar Comments are addressed. The revisions are uploaded in GitHub. Please take a look and let me know if the revisions meet your requirements or not. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for addressing my comments @lgu1234
Comments addressed, CI is happy. I just kicked Mac CI and it's coming online again, but I have no concerns. I'm going to merge this in. |
Pull request overview
This new feature will allow EnergyPlus to model an outside air system supplying ventilation air to multiple air handlers.
Work Checklist
Add to this list or remove from it as applicable. This is a simple templated set of guidelines.
Review Checklist
This will not be exhaustively relevant to every PR.