Repository: NREL/OpenStudio (language: python)

Issue:
Enable Heat Recovery Chiller Fields
Enhancement Request
--------------
This is a feature request to enable heat recovery chiller modeling in OpenStudio.

## Detailed Description
More specifically, this is a feature request to:
(1) enable and fix field setters for heat recovery chiller inputs
(2) provide a working openstudio example model with a heat recovery chiller

Background:
I'm attempting to emulate a heat recovery chiller scheme in OpenStudio that is doable in EnergyPlus.
See the FTLB_HRC.idf attached.

[heat_recovery_chiller.zip](https://github.com/NREL/OpenStudio/files/9863948/heat_recovery_chiller.zip)

I have a ruby script that adjusts a base model equipment temperatures, adds a heat recovery loop, and adds a heat recovery chiller.  However, I cannot manage to set the `ChillerElectricEIR` fields to enable heat recovery operation.  The `setHeatRecoveryLeavingTemperatureSetpointNode` method runs without an error, but fails to set the node in the model.  Also, there is no method to set the '`Heat Recovery Inlet Node Name`' and '`Heat Recovery Outlet Node Name`' fields.

Related to #4311

## Possible Implementation
Setting node names directly is probably not what we want.  Typically we add equipment with the `addSupplyBranchForComponent()` or `addDemandBranchForComponent()` methods on a `PlantLoop` object.  One options is to make an `addHeatRecoveryBranchForComponent` method that sets the nodes for the chiller, or a method tied to the chiller object `addSupplyHeatRecoveryBranch(<OS:PlantLoop>)` (preferred).
Enable Heat Recovery Chiller Fields
Enhancement Request
--------------
This is a feature request to enable heat recovery chiller modeling in OpenStudio.

## Detailed Description
More specifically, this is a feature request to:
(1) enable and fix field setters for heat recovery chiller inputs
(2) provide a working openstudio example model with a heat recovery chiller

Background:
I'm attempting to emulate a heat recovery chiller scheme in OpenStudio that is doable in EnergyPlus.
See the FTLB_HRC.idf attached.

[heat_recovery_chiller.zip](https://github.com/NREL/OpenStudio/files/9863948/heat_recovery_chiller.zip)

I have a ruby script that adjusts a base model equipment temperatures, adds a heat recovery loop, and adds a heat recovery chiller.  However, I cannot manage to set the `ChillerElectricEIR` fields to enable heat recovery operation.  The `setHeatRecoveryLeavingTemperatureSetpointNode` method runs without an error, but fails to set the node in the model.  Also, there is no method to set the '`Heat Recovery Inlet Node Name`' and '`Heat Recovery Outlet Node Name`' fields.

Related to #4311

## Possible Implementation
Setting node names directly is probably not what we want.  Typically we add equipment with the `addSupplyBranchForComponent()` or `addDemandBranchForComponent()` methods on a `PlantLoop` object.  One options is to make an `addHeatRecoveryBranchForComponent` method that sets the nodes for the chiller, or a method tied to the chiller object `addSupplyHeatRecoveryBranch(<OS:PlantLoop>)` (preferred).


Discussion / hints:
@mdahlhausen This is already partially implemented.

```ruby
include OpenStudio::Model
m = Model.new
cndLoop = PlantLoop.new(m)
chwLoop = PlantLoop.new(m)
hrLoop = PlantLoop.new(m)
ch = ChillerElectricEIR.new(m)
chwLoop.addSupplyBranchForComponent(ch)
cndLoop.addDemandBranchForComponent(ch)
hrLoop.addDemandBranchForComponent(ch)
=> [openstudio.model.ChillerElectricEIR] <0> Calling addToTertiaryNode to connect it to the tertiary (=Heat Recovery Loop) loop for Object of type 'OS:Chiller:Electric:EIR' and named 'Chiller Electric EIR 1'
=> true
puts ch
```


```diff
OS:Chiller:Electric:EIR,
  {b040fa1f-f206-454e-ac2e-6468c3bba8ad}, !- Handle
  Chiller Electric EIR 1,                 !- Name
  Autosize,                               !- Reference Capacity {W}
  5.5,                                    !- Reference COP {W/W}
  ,                                       !- Reference Leaving Chilled Water Temperature {C}
  ,                                       !- Reference Entering Condenser Fluid Temperature {C}
  Autosize,                               !- Reference Chilled Water Flow Rate {m3/s}
  ,                                       !- Reference Condenser Fluid Flow Rate {m3/s}
  {69d8bb32-352d-4999-847f-752d935bab2f}, !- Cooling Capacity Function of Temperature Curve Name
  {523248f1-9a17-4af3-911a-be3cc9e0a4b8}, !- Electric Input to Cooling Output Ratio Function of Temperature Curve Name
  {39b45e88-985a-4476-97c9-6ac2d9de79b8}, !- Electric Input to Cooling Output Ratio Function of Part Load Ratio Curve Name
  ,                                       !- Minimum Part Load Ratio
  ,                                       !- Maximum Part Load Ratio
  ,                                       !- Optimum Part Load Ratio
  ,                                       !- Minimum Unloading Ratio
  {cff9f947-287b-416b-a171-c0af07a90f97}, !- Chilled Water Inlet Node Name
  {2375e7b9-f079-4fcc-91b4-9856313313cd}, !- Chilled Water Outlet Node Name
  {64143aad-a2c0-45fe-930f-9dd0f50d41e3}, !- Condenser Inlet Node Name
  {ba07517c-528c-4958-b585-2d957ecb1e6b}, !- Condenser Outlet Node Name
  WaterCooled,                            !- Condenser Type
  ,                                       !- Condenser Fan Power Ratio {W/W}
  ,                                       !- Fraction of Compressor Electric Consumption Rejected by Condenser
  ,                                       !- Leaving Chilled Water Lower Temperature Limit {C}
  ,                                       !- Chiller Flow Mode
  Autosize,                               !- Design Heat Recovery Water Flow Rate {m3/s}
+  {1858909b-3abb-40b9-8983-d3de191752f0}, !- Heat Recovery Inlet Node Name
+  {4188fe54-d459-486f-b6b5-c126678de0f5}, !- Heat Recovery Outlet Node Name
  1,                                      !- Sizing Factor
  0,                                      !- Basin Heater Capacity {W/K}
  10,                                     !- Basin Heater Setpoint Temperature {C}
  ,                                       !- Basin Heater Operating Schedule Name
  1,                                      !- Condenser Heat Recovery Relative Capacity Fraction
  ,                                       !- Heat Recovery Inlet High Temperature Limit Schedule Name
  ,                                       !- Heat Recovery Leaving Temperature Setpoint Node Name
  General;                                !- End-Use Subcategory
```
If you want to be explicit about which loop you intend to connect to, you can also always call chiller.addToTertiaryNode (but you need to make your own branch yourself).

I am seeing the issue with the `setHeatRecoveryLeavingTemperatureSetpointNode` though. It's an IDD issue.

```diff
--- a/resources/model/OpenStudio.idd
+++ b/resources/model/OpenStudio.idd
@@ -13597,11 +13597,11 @@ OS:Chiller:Electric:EIR,
   A16, \field Heat Recovery Leaving Temperature Setpoint Node Name
        \note This optional field provides control over the heat recovery
        \note Using this triggers a model more suited to series bundle and chillers with higher temperature heat recovery
        \note If this field is not used, the bundles are modeled as being in parallel
        \type object-list
-       \object-list ConnectionNames
+       \object-list Node
   A17; \field End-Use Subcategory

```


Damn, I suppose you'll also want to put a SPM on that node, like a SetpointManager::Scheduled. Except we disallow placing a SPM on the demand side of a plant loop when the node is on a branch... Before the Splitter, after the Mixer is fine though...

I'm torn between removing this check (and remove a valid check for the vast majority of users), or force advanced users doing HR to use the mixer outlet node instead.

Edit: Or use the setString/setPointer approach...

```ruby
hrOutletNode = ch.tertiaryOutletModelObject.get.to_Node.get
spm = SetpointManagerScheduled.new(m, ScheduleConstant.new(m))
spm.addToNode(hrOutletNode)
[openstudio.model.SetpointManager] <-1> Object of type 'OS:SetpointManager:Scheduled' and named 'Setpoint Manager Scheduled 1' cannot be added on a demand branch
=> false
```

```ruby
idx = spm.iddObject.numFields.times.select{|i| spm.iddObject.getField(i).get.name.include?('Setpoint Node') }.first
raise if idx.nil?
spm.setPointer(idx, hrOutletNode.handle())
=> true

puts spm

OS:SetpointManager:Scheduled,
  {61d7be49-a724-43b5-995d-8f26e77eacc9}, !- Handle
  Setpoint Manager Scheduled 1,           !- Name
  Temperature,                            !- Control Variable
  {d959a999-a4b2-4380-8cdc-56f715f88c51}, !- Schedule Name
  {2e8204e0-8a54-4307-a993-2df5c56d50ab}; !- Setpoint Node or NodeList Name
```
@mdahlhausen This is already partially implemented.

```ruby
include OpenStudio::Model
m = Model.new
cndLoop = PlantLoop.new(m)
chwLoop = PlantLoop.new(m)
hrLoop = PlantLoop.new(m)
ch = ChillerElectricEIR.new(m)
chwLoop.addSupplyBranchForComponent(ch)
cndLoop.addDemandBranchForComponent(ch)
hrLoop.addDemandBranchForComponent(ch)
=> [openstudio.model.ChillerElectricEIR] <0> Calling addToTertiaryNode to connect it to the tertiary (=Heat Recovery Loop) loop for Object of type 'OS:Chiller:Electric:EIR' and named 'Chiller Electric EIR 1'
=> true
puts ch
```


```diff
OS:Chiller:Electric:EIR,
  {b040fa1f-f206-454e-ac2e-6468c3bba8ad}, !- Handle
  Chiller Electric EIR 1,                 !- Name
  Autosize,                               !- Reference Capacity {W}
  5.5,                                    !- Reference COP {W/W}
  ,                                       !- Reference Leaving Chilled Water Temperature {C}
  ,                                       !- Reference Entering Condenser Fluid Temperature {C}
  Autosize,                               !- Reference Chilled Water Flow Rate {m3/s}
  ,                                       !- Reference Condenser Fluid Flow Rate {m3/s}
  {69d8bb32-352d-4999-847f-752d935bab2f}, !- Cooling Capacity Function of Temperature Curve Name
  {523248f1-9a17-4af3-911a-be3cc9e0a4b8}, !- Electric Input to Cooling Output Ratio Function of Temperature Curve Name
  {39b45e88-985a-4476-97c9-6ac2d9de79b8}, !- Electric Input to Cooling Output Ratio Function of Part Load Ratio Curve Name
  ,                                       !- Minimum Part Load Ratio
  ,                                       !- Maximum Part Load Ratio
  ,                                       !- Optimum Part Load Ratio
  ,                                       !- Minimum Unloading Ratio
  {cff9f947-287b-416b-a171-c0af07a90f97}, !- Chilled Water Inlet Node Name
  {2375e7b9-f079-4fcc-91b4-9856313313cd}, !- Chilled Water Outlet Node Name
  {64143aad-a2c0-45fe-930f-9dd0f50d41e3}, !- Condenser Inlet Node Name
  {ba07517c-528c-4958-b585-2d957ecb1e6b}, !- Condenser Outlet Node Name
  WaterCooled,                            !- Condenser Type
  ,                                       !- Condenser Fan Power Ratio {W/W}
  ,                                       !- Fraction of Compressor Electric Consumption Rejected by Condenser
  ,                                       !- Leaving Chilled Water Lower Temperature Limit {C}
  ,                                       !- Chiller Flow Mode
  Autosize,                               !- Design Heat Recovery Water Flow Rate {m3/s}
+  {1858909b-3abb-40b9-8983-d3de191752f0}, !- Heat Recovery Inlet Node Name
+  {4188fe54-d459-486f-b6b5-c126678de0f5}, !- Heat Recovery Outlet Node Name
  1,                                      !- Sizing Factor
  0,                                      !- Basin Heater Capacity {W/K}
  10,                                     !- Basin Heater Setpoint Temperature {C}
  ,                                       !- Basin Heater Operating Schedule Name
  1,                                      !- Condenser Heat Recovery Relative Capacity Fraction
  ,                                       !- Heat Recovery Inlet High Temperature Limit Schedule Name
  ,                                       !- Heat Recovery Leaving Temperature Setpoint Node Name
  General;                                !- End-Use Subcategory
```
If you want to be explicit about which loop you intend to connect to, you can also always call chiller.addToTertiaryNode (but you need to make your own branch yourself).

I am seeing the issue with the `setHeatRecoveryLeavingTemperatureSetpointNode` though. It's an IDD issue.

```diff
--- a/resources/model/OpenStudio.idd
+++ b/resources/model/OpenStudio.idd
@@ -13597,11 +13597,11 @@ OS:Chiller:Electric:EIR,
   A16, \field Heat Recovery Leaving Temperature Setpoint Node Name
        \note This optional field provides control over the heat recovery
        \note Using this triggers a model more suited to series bundle and chillers with higher temperature heat recovery
        \note If this field is not used, the bundles are modeled as being in parallel
        \type object-list
-       \object-list ConnectionNames
+       \object-list Node
   A17; \field End-Use Subcategory

```


Damn, I suppose you'll also want to put a SPM on that node, like a SetpointManager::Scheduled. Except we disallow placing a SPM on the demand side of a plant loop when the node is on a branch... Before the Splitter, after the Mixer is fine though...

I'm torn between removing this check (and remove a valid check for the vast majority of users), or force advanced users doing HR to use the mixer outlet node instead.

Edit: Or use the setString/setPointer approach...

```ruby
hrOutletNode = ch.tertiaryOutletModelObject.get.to_Node.get
spm = SetpointManagerScheduled.new(m, ScheduleConstant.new(m))
spm.addToNode(hrOutletNode)
[openstudio.model.SetpointManager] <-1> Object of type 'OS:SetpointManager:Scheduled' and named 'Setpoint Manager Scheduled 1' cannot be added on a demand branch
=> false
```

```ruby
idx = spm.iddObject.numFields.times.select{|i| spm.iddObject.getField(i).get.name.include?('Setpoint Node') }.first
raise if idx.nil?
spm.setPointer(idx, hrOutletNode.handle())
=> true

puts spm

OS:SetpointManager:Scheduled,
  {61d7be49-a724-43b5-995d-8f26e77eacc9}, !- Handle
  Setpoint Manager Scheduled 1,           !- Name
  Temperature,                            !- Control Variable
  {d959a999-a4b2-4380-8cdc-56f715f88c51}, !- Schedule Name
  {2e8204e0-8a54-4307-a993-2df5c56d50ab}; !- Setpoint Node or NodeList Name
```

Fix patch:
diff --git a/Jenkinsfile_develop_osx b/Jenkinsfile_develop_osx
index 5226bdb90e0..d7d18e8711f 100644
--- a/Jenkinsfile_develop_osx
+++ b/Jenkinsfile_develop_osx
@@ -1,6 +1,6 @@
 //Jenkins pipelines are stored in shared libaries. Please see: https://github.com/NREL/cbci_jenkins_libs
 
-@Library('cbci_shared_libs@pythonengine') _
+@Library('cbci_shared_libs') _
 
 // Build for PR to develop branch only.
 if ((env.CHANGE_ID) && (env.CHANGE_TARGET) ) {
diff --git a/Jenkinsfile_develop_ubuntu_1804 b/Jenkinsfile_develop_ubuntu_1804
index 149882c9e9e..d056d7939a9 100644
--- a/Jenkinsfile_develop_ubuntu_1804
+++ b/Jenkinsfile_develop_ubuntu_1804
@@ -1,7 +1,7 @@
 
 //Jenkins pipelines are stored in shared libaries. Please see: https://github.com/NREL/cbci_jenkins_libs
 
-@Library('cbci_shared_libs@pythonengine') _
+@Library('cbci_shared_libs') _
 
 // Build for PR to develop branch only.
 if ((env.CHANGE_ID) && (env.CHANGE_TARGET) ) {
diff --git a/Jenkinsfile_develop_ubuntu_2004 b/Jenkinsfile_develop_ubuntu_2004
index 0d841d799f4..1c9eea6c747 100644
--- a/Jenkinsfile_develop_ubuntu_2004
+++ b/Jenkinsfile_develop_ubuntu_2004
@@ -1,7 +1,7 @@
 
 //Jenkins pipelines are stored in shared libaries. Please see: https://github.com/NREL/cbci_jenkins_libs
 
-@Library('cbci_shared_libs@pythonengine') _
+@Library('cbci_shared_libs') _
 
 // Build for PR to develop branch only.
 if ((env.CHANGE_ID) && (env.CHANGE_TARGET) ) {
diff --git a/Jenkinsfile_develop_windows b/Jenkinsfile_develop_windows
index f7c055b5c35..cb285700d5a 100644
--- a/Jenkinsfile_develop_windows
+++ b/Jenkinsfile_develop_windows
@@ -1,6 +1,6 @@
 //Jenkins pipelines are stored in shared libaries. Please see: https://github.com/NREL/cbci_jenkins_libs
 
-@Library('cbci_shared_libs@pythonengine') _
+@Library('cbci_shared_libs') _
 
 // Build for PR to develop branch only.
 if ((env.CHANGE_ID) && (env.CHANGE_TARGET) ) {
diff --git a/resources/model/OpenStudio.idd b/resources/model/OpenStudio.idd
index c1a9ace2d65..14e05494282 100644
--- a/resources/model/OpenStudio.idd
+++ b/resources/model/OpenStudio.idd
@@ -13599,7 +13599,7 @@ OS:Chiller:Electric:EIR,
        \note Using this triggers a model more suited to series bundle and chillers with higher temperature heat recovery
        \note If this field is not used, the bundles are modeled as being in parallel
        \type object-list
-       \object-list ConnectionNames
+       \object-list Node
   A17; \field End-Use Subcategory
        \note Any text may be used here to categorize the end-uses in the ABUPS End Uses by Subcategory table.
        \type alpha


Test patch:
diff --git a/src/model/test/ChillerElectricEIR_GTest.cpp b/src/model/test/ChillerElectricEIR_GTest.cpp
index 94df1be5513..c0f53ddceef 100644
--- a/src/model/test/ChillerElectricEIR_GTest.cpp
+++ b/src/model/test/ChillerElectricEIR_GTest.cpp
@@ -714,3 +714,51 @@ TEST_F(ModelFixture, ChillerElectricEIR_ElectricInputToCoolingOutputRatioFunctio
     EXPECT_EQ(curveCubic, chiller.electricInputToCoolingOutputRatioFunctionOfPLR());
   }
 }
+
+TEST_F(ModelFixture, ChillerElectricEIR_HeatRecoverySetpointNode) {
+
+  // Test for #4724
+  model::Model model;
+  model::ChillerElectricEIR chiller(model);
+
+  // Chilled Water Loop: on the supply side
+  model::PlantLoop chwLoop(model);
+  EXPECT_TRUE(chwLoop.addSupplyBranchForComponent(chiller));
+  EXPECT_TRUE(chiller.plantLoop());
+  EXPECT_TRUE(chiller.chilledWaterLoop());
+
+  // Condenser Loop: on the demand side
+  model::PlantLoop cndwLoop(model);
+  EXPECT_TRUE(cndwLoop.addDemandBranchForComponent(chiller));
+  EXPECT_TRUE(chiller.secondaryPlantLoop());
+  EXPECT_TRUE(chiller.condenserWaterLoop());
+
+  model::PlantLoop hrLoop(model);
+  EXPECT_TRUE(hrLoop.addDemandBranchForComponent(chiller));
+  EXPECT_TRUE(chiller.tertiaryPlantLoop());
+  EXPECT_TRUE(chiller.heatRecoveryLoop());
+
+  // Double check everything is fine now
+  ASSERT_TRUE(chiller.plantLoop());
+  EXPECT_EQ(chwLoop, chiller.plantLoop().get());
+  ASSERT_TRUE(chiller.chilledWaterLoop());
+  EXPECT_EQ(chwLoop, chiller.chilledWaterLoop().get());
+
+  ASSERT_TRUE(chiller.secondaryPlantLoop());
+  EXPECT_EQ(cndwLoop, chiller.secondaryPlantLoop().get());
+  ASSERT_TRUE(chiller.condenserWaterLoop());
+  EXPECT_EQ(cndwLoop, chiller.condenserWaterLoop().get());
+
+  ASSERT_TRUE(chiller.tertiaryPlantLoop());
+  EXPECT_EQ(hrLoop, chiller.tertiaryPlantLoop().get());
+  ASSERT_TRUE(chiller.heatRecoveryLoop());
+  EXPECT_EQ(hrLoop, chiller.heatRecoveryLoop().get());
+
+  // Get the Chiller Tertirary Outlet Node = HR Loop Outlet Node
+  ASSERT_TRUE(chiller.tertiaryOutletModelObject());
+  auto hrOutletNode = chiller.tertiaryOutletModelObject()->cast<model::Node>();
+  // Actual test for #4724
+  EXPECT_TRUE(chiller.setHeatRecoveryLeavingTemperatureSetpointNode(hrOutletNode));
+  ASSERT_TRUE(chiller.heatRecoveryLeavingTemperatureSetpointNode());
+  EXPECT_EQ(hrOutletNode, chiller.heatRecoveryLeavingTemperatureSetpointNode().get());
+}