Repository: NREL/OpenStudio (language: python)

Issue:
Default new CoilCoolingWaterToAirHeatPumpEquationFit fields in forward translator
Issue overview
--------------
OS 3.7 adds several new fields to the CoilCoolingWaterToAirHeatPumpEquationFit object:
- Part Load Fraction Correlation Curve Name
- Nominal Time for Condensate Removal to Begin {s}
- Ratio of Initial Moisture Evaporation Rate and Steady State Latent Capacity {dimensionless}
- Maximum Cycling Rate {cycles/hr}
- Latent Capacity Time Constant {s}
- Fan Delay Time {s}

The defaults are not aligned between OS object creation and the version translator.

## Current Behavior
In the forward translator, the defaults for these new fields are different than the defaults used when updating an older .osm using the version translator.
The result is a regression error:
```
For OS:Coil:Cooling:WaterToAirHeatPump:EquationFit called 'T SW Apartment ZN PSZ-AC Water-to-Air HP Clg Coil 20kBtu/hr 13.0EER'  'Latent Capacity Time Constant': true model = '60.0', compare model = '0.0'
For OS:Coil:Cooling:WaterToAirHeatPump:EquationFit called 'T SW Apartment ZN PSZ-AC Water-to-Air HP Clg Coil 20kBtu/hr 13.0EER'  'Maximum Cycling Rate': true model = '2.5', compare model = '0.0'
```

## Expected Behavior
Either the OS defaults should change, or the version translator should set the new defaults upon translation to avoid regression errors.

## Steps to Reproduce
1. Create a model with CoilCoolingWaterToAirHeatPumpEquationFit object in OS 3.6.1
2. Forward translate it with OS develop
3. Create a model with CoilCoolingWaterToAirHeatPumpEquationFit in OS develop
4. compare

### Context
impacting openstudio-standards regression tests

Default new CoilCoolingWaterToAirHeatPumpEquationFit fields in forward translator
Issue overview
--------------
OS 3.7 adds several new fields to the CoilCoolingWaterToAirHeatPumpEquationFit object:
- Part Load Fraction Correlation Curve Name
- Nominal Time for Condensate Removal to Begin {s}
- Ratio of Initial Moisture Evaporation Rate and Steady State Latent Capacity {dimensionless}
- Maximum Cycling Rate {cycles/hr}
- Latent Capacity Time Constant {s}
- Fan Delay Time {s}

The defaults are not aligned between OS object creation and the version translator.

## Current Behavior
In the forward translator, the defaults for these new fields are different than the defaults used when updating an older .osm using the version translator.
The result is a regression error:
```
For OS:Coil:Cooling:WaterToAirHeatPump:EquationFit called 'T SW Apartment ZN PSZ-AC Water-to-Air HP Clg Coil 20kBtu/hr 13.0EER'  'Latent Capacity Time Constant': true model = '60.0', compare model = '0.0'
For OS:Coil:Cooling:WaterToAirHeatPump:EquationFit called 'T SW Apartment ZN PSZ-AC Water-to-Air HP Clg Coil 20kBtu/hr 13.0EER'  'Maximum Cycling Rate': true model = '2.5', compare model = '0.0'
```

## Expected Behavior
Either the OS defaults should change, or the version translator should set the new defaults upon translation to avoid regression errors.

## Steps to Reproduce
1. Create a model with CoilCoolingWaterToAirHeatPumpEquationFit object in OS 3.6.1
2. Forward translate it with OS develop
3. Create a model with CoilCoolingWaterToAirHeatPumpEquationFit in OS develop
4. compare

### Context
impacting openstudio-standards regression tests



Discussion / hints:
First, just to clarify:

* Our version translator does the same as the E+ IDFVersionUpdater would do
* Our Ctor defaults these fields to the same as E+ 23.2.0
* The discrepancy you are seeing is because of that. I reported this new discrepancy to the E+ team when I noticed it when working on the upgrade to 23.2.0-IOFreeze, see this table at made at that time:

![image (10)](https://github.com/NREL/OpenStudio/assets/5479063/e9d2ceb6-b0b2-43a6-826c-194e0d04ede2)

It seems that:
* These factor were set by the parent object, which defaulted to these values
    * (It's also possible that some of these values were actually not set by the parent object but defaulting to these values internally, something I've been told but I have no confirmed) 
* But the default behavior for other coils is to generally zero-out latent degradation...
Now:
* I can't touch the VersionTranslator, as this would create a regression. The behavior of a 3.6.1 model should be preserved when updating to 3.7.0
* Thus, there are only two possible paths:
    * Have our SDK Ctor use a different default than E+. I'm not a huge fan of this idea, generally speaking, because it's a bit confusing, but it's maybe fine. (I'm going to prepare a PR that does this, so we have CI running on it and you can test, we can decide if we keep it or trash it)
    * You, on openstudio-standards, set these fields explicitly. @mdahlhausen is that an option? (I don't know what are the side effects of doing this)



---- 

To be perfectly clear (but add to the confusion), What our VersionTranslator does is actually this:
* If the coil was inside a parent unitary system, basically use the values there or the default of cycling rate=2.5 / latent time = 60.0
* If not, use 0.0 and 0.0 (like the Ctor currently does).
For reference:

```
$ rg -g '*.cpp'  --max-depth 1 "setLatentCapacityTimeConstant\([\d.]+\)"
CoilCoolingDXVariableSpeed.cpp
743:    ok = setLatentCapacityTimeConstant(60.0);
795:    ok = setLatentCapacityTimeConstant(60.0);

CoilCoolingDXCurveFitOperatingMode.cpp
459:    setLatentCapacityTimeConstant(0.0);

CoilCoolingDXSingleSpeed.cpp
960:    setLatentCapacityTimeConstant(0.0);
1000:    setLatentCapacityTimeConstant(0.0);

CoilCoolingDXMultiSpeedStageData.cpp
628:    ok = setLatentCapacityTimeConstant(0.0);
675:    ok = setLatentCapacityTimeConstant(0.0);

CoilPerformanceDXCooling.cpp
590:    setLatentCapacityTimeConstant(45);
614:    setLatentCapacityTimeConstant(45);

CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFit.cpp
520:    ok = setLatentCapacityTimeConstant(0.0);
558:    ok = setLatentCapacityTimeConstant(0.0);

CoilCoolingWaterToAirHeatPumpEquationFit.cpp
594:      ok = setLatentCapacityTimeConstant(0.0);
```
First, just to clarify:

* Our version translator does the same as the E+ IDFVersionUpdater would do
* Our Ctor defaults these fields to the same as E+ 23.2.0
* The discrepancy you are seeing is because of that. I reported this new discrepancy to the E+ team when I noticed it when working on the upgrade to 23.2.0-IOFreeze, see this table at made at that time:

![image (10)](https://github.com/NREL/OpenStudio/assets/5479063/e9d2ceb6-b0b2-43a6-826c-194e0d04ede2)

It seems that:
* These factor were set by the parent object, which defaulted to these values
    * (It's also possible that some of these values were actually not set by the parent object but defaulting to these values internally, something I've been told but I have no confirmed) 
* But the default behavior for other coils is to generally zero-out latent degradation...
Now:
* I can't touch the VersionTranslator, as this would create a regression. The behavior of a 3.6.1 model should be preserved when updating to 3.7.0
* Thus, there are only two possible paths:
    * Have our SDK Ctor use a different default than E+. I'm not a huge fan of this idea, generally speaking, because it's a bit confusing, but it's maybe fine. (I'm going to prepare a PR that does this, so we have CI running on it and you can test, we can decide if we keep it or trash it)
    * You, on openstudio-standards, set these fields explicitly. @mdahlhausen is that an option? (I don't know what are the side effects of doing this)



---- 

To be perfectly clear (but add to the confusion), What our VersionTranslator does is actually this:
* If the coil was inside a parent unitary system, basically use the values there or the default of cycling rate=2.5 / latent time = 60.0
* If not, use 0.0 and 0.0 (like the Ctor currently does).
For reference:

```
$ rg -g '*.cpp'  --max-depth 1 "setLatentCapacityTimeConstant\([\d.]+\)"
CoilCoolingDXVariableSpeed.cpp
743:    ok = setLatentCapacityTimeConstant(60.0);
795:    ok = setLatentCapacityTimeConstant(60.0);

CoilCoolingDXCurveFitOperatingMode.cpp
459:    setLatentCapacityTimeConstant(0.0);

CoilCoolingDXSingleSpeed.cpp
960:    setLatentCapacityTimeConstant(0.0);
1000:    setLatentCapacityTimeConstant(0.0);

CoilCoolingDXMultiSpeedStageData.cpp
628:    ok = setLatentCapacityTimeConstant(0.0);
675:    ok = setLatentCapacityTimeConstant(0.0);

CoilPerformanceDXCooling.cpp
590:    setLatentCapacityTimeConstant(45);
614:    setLatentCapacityTimeConstant(45);

CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFit.cpp
520:    ok = setLatentCapacityTimeConstant(0.0);
558:    ok = setLatentCapacityTimeConstant(0.0);

CoilCoolingWaterToAirHeatPumpEquationFit.cpp
594:      ok = setLatentCapacityTimeConstant(0.0);
```

Fix patch:
diff --git a/src/model/CoilCoolingWaterToAirHeatPumpEquationFit.cpp b/src/model/CoilCoolingWaterToAirHeatPumpEquationFit.cpp
index 30c70fa6316..5732bdbdb0b 100644
--- a/src/model/CoilCoolingWaterToAirHeatPumpEquationFit.cpp
+++ b/src/model/CoilCoolingWaterToAirHeatPumpEquationFit.cpp
@@ -582,6 +582,7 @@ namespace model {
       ok = setRatedEnteringAirWetBulbTemperature(19.0);
       OS_ASSERT(ok);
 
+      // E+ 23.2.0 defaults Maximum Cycling Rate and Latent Capacity Time Constant to 0.0, we don't, cf #4999
       constexpr double maximumCyclingRatePerHour = 2.5;
       constexpr double heatPumpTimeConstantSeconds = 60.0;
       const CurveLinear plfCorrelation =
@@ -589,9 +590,9 @@ namespace model {
 
       ok = setPartLoadFractionCorrelationCurve(plfCorrelation);
 
-      ok = setMaximumCyclingRate(0.0);
+      ok = setMaximumCyclingRate(maximumCyclingRatePerHour);
       OS_ASSERT(ok);
-      ok = setLatentCapacityTimeConstant(0.0);
+      ok = setLatentCapacityTimeConstant(heatPumpTimeConstantSeconds);
       OS_ASSERT(ok);
       ok = setFanDelayTime(60.0);
       OS_ASSERT(ok);
diff --git a/src/model/CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFit.cpp b/src/model/CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFit.cpp
index ce5366f4d80..43ac9c4134c 100644
--- a/src/model/CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFit.cpp
+++ b/src/model/CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFit.cpp
@@ -515,14 +515,16 @@ namespace model {
     OS_ASSERT(ok);
     ok = setInitialMoistureEvaporationRateDividedbySteadyStateACLatentCapacity(0);
     OS_ASSERT(ok);
-    ok = setMaximumCyclingRate(0.0);
+    // E+ 23.2.0 defaults Maximum Cycling Rate and Latent Capacity Time Constant to 0.0, we don't, cf #4999
+    ok = setMaximumCyclingRate(2.5);
     OS_ASSERT(ok);
-    ok = setLatentCapacityTimeConstant(0.0);
+    ok = setLatentCapacityTimeConstant(60.0);
     OS_ASSERT(ok);
     ok = setFanDelayTime(60.0);
     OS_ASSERT(ok);
     setUseHotGasReheat(false);
 
+    // Note: this predates v23.2.0. Consider switching to CurveLinear::defaultHeatPumpCoilPLFCorrelationCurve (would calculate c1=0.8337, c2=0.1662)
     auto partLoadFraction = CurveQuadratic(model);
     partLoadFraction.setCoefficient1Constant(0.85);
     partLoadFraction.setCoefficient2x(0.15);
@@ -553,9 +555,10 @@ namespace model {
     OS_ASSERT(ok);
     ok = setInitialMoistureEvaporationRateDividedbySteadyStateACLatentCapacity(0);
     OS_ASSERT(ok);
-    ok = setMaximumCyclingRate(0.0);
+    // E+ 23.2.0 defaults Maximum Cycling Rate and Latent Capacity Time Constant to 0.0, we don't, cf #4999
+    ok = setMaximumCyclingRate(2.5);
     OS_ASSERT(ok);
-    ok = setLatentCapacityTimeConstant(0.0);
+    ok = setLatentCapacityTimeConstant(60.0);
     OS_ASSERT(ok);
     ok = setFanDelayTime(60.0);
     OS_ASSERT(ok);
diff --git a/src/osversion/VersionTranslator.cpp b/src/osversion/VersionTranslator.cpp
index 390039785f3..96677847b25 100644
--- a/src/osversion/VersionTranslator.cpp
+++ b/src/osversion/VersionTranslator.cpp
@@ -8513,8 +8513,9 @@ namespace osversion {
         const std::string curveName = hasCoilInfo ? it->curveName() : fmt::format("{}-PLFCorrelationCurve", object.nameString());
         newObject.setString(17, curveName);
 
-        double maxCyclingRate = 0.0;
-        double heatPumpTimeConst = 0.0;
+        // E+ 23.2.0 defaults Maximum Cycling Rate and Latent Capacity Time Constant to 0.0, we don't, cf #4999
+        double maxCyclingRate = 2.5;
+        double heatPumpTimeConst = 60.0;
         double hpDelayTime = 60.0;
         if (hasCoilInfo) {
           maxCyclingRate = it->maxCyclingRate;
@@ -8560,8 +8561,9 @@ namespace osversion {
         auto it = CoilLatentTransitionInfo::findFromCoolingCoil(coilTransitionInfos, object);
         const bool hasCoilInfo = (it != coilTransitionInfos.end());
 
-        double maxCyclingRate = 0.0;
-        double heatPumpTimeConst = 0.0;
+        // E+ 23.2.0 defaults Maximum Cycling Rate and Latent Capacity Time Constant to 0.0, we don't, cf #4999
+        double maxCyclingRate = 2.5;
+        double heatPumpTimeConst = 60.0;
         double hpDelayTime = 60.0;
         if (hasCoilInfo) {
           maxCyclingRate = it->maxCyclingRate;


Test patch:
diff --git a/src/model/test/CoilCoolingWaterToAirHeatPumpEquationFit_GTest.cpp b/src/model/test/CoilCoolingWaterToAirHeatPumpEquationFit_GTest.cpp
index 97f9931cc53..b4550e2218d 100644
--- a/src/model/test/CoilCoolingWaterToAirHeatPumpEquationFit_GTest.cpp
+++ b/src/model/test/CoilCoolingWaterToAirHeatPumpEquationFit_GTest.cpp
@@ -246,9 +246,10 @@ TEST_F(ModelFixture, CoilCoolingWaterToAirHeatPumpEquationFit_2320_NewFields) {
 
   CoilCoolingWaterToAirHeatPumpEquationFit coil(m);
 
+  // E+ 23.2.0 defaults Maximum Cycling Rate and Latent Capacity Time Constant to 0.0, we don't, cf #4999
+  EXPECT_EQ(2.5, coil.maximumCyclingRate());
+  EXPECT_EQ(60.0, coil.latentCapacityTimeConstant());
   // Test IDD defaults
-  EXPECT_EQ(0.0, coil.maximumCyclingRate());
-  EXPECT_EQ(0.0, coil.latentCapacityTimeConstant());
   EXPECT_EQ(60.0, coil.fanDelayTime());
 
   EXPECT_TRUE(coil.setMaximumCyclingRate(3.5));
diff --git a/src/model/test/CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFit_GTest.cpp b/src/model/test/CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFit_GTest.cpp
index 04f9f805322..1651e98de09 100644
--- a/src/model/test/CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFit_GTest.cpp
+++ b/src/model/test/CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFit_GTest.cpp
@@ -47,9 +47,10 @@ TEST_F(ModelFixture, CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFit_2320_
 
   CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFit coil(m);
 
+  // E+ 23.2.0 defaults Maximum Cycling Rate and Latent Capacity Time Constant to 0.0, we don't, cf #4999
+  EXPECT_EQ(2.5, coil.maximumCyclingRate());
+  EXPECT_EQ(60.0, coil.latentCapacityTimeConstant());
   // Test IDD defaults
-  EXPECT_EQ(0.0, coil.maximumCyclingRate());
-  EXPECT_EQ(0.0, coil.latentCapacityTimeConstant());
   EXPECT_EQ(60.0, coil.fanDelayTime());
 
   EXPECT_TRUE(coil.setMaximumCyclingRate(3.5));
diff --git a/src/osversion/test/VersionTranslator_GTest.cpp b/src/osversion/test/VersionTranslator_GTest.cpp
index b894320c1e3..906a56e500a 100644
--- a/src/osversion/test/VersionTranslator_GTest.cpp
+++ b/src/osversion/test/VersionTranslator_GTest.cpp
@@ -3106,8 +3106,9 @@ TEST_F(OSVersionFixture, update_3_6_1_to_3_7_0_Coils_Latent_solo) {
     {
       const size_t insertionIndex = 20;
       // No Unitary -> IDD DEFAULTS
-      EXPECT_EQ(0.0, coil.getDouble(insertionIndex).get());       // Maximum Cycling Rate
-      EXPECT_EQ(0.0, coil.getDouble(insertionIndex + 1).get());   // Latent Capacity Time Constant
+      // E+ 23.2.0 defaults Maximum Cycling Rate and Latent Capacity Time Constant to 0.0, we don't, cf #4999
+      EXPECT_EQ(2.5, coil.getDouble(insertionIndex).get());       // Maximum Cycling Rate
+      EXPECT_EQ(60.0, coil.getDouble(insertionIndex + 1).get());  // Latent Capacity Time Constant
       EXPECT_EQ(60.0, coil.getDouble(insertionIndex + 2).get());  // Fan Delay Time
     }
   }
@@ -3124,8 +3125,9 @@ TEST_F(OSVersionFixture, update_3_6_1_to_3_7_0_Coils_Latent_solo) {
       EXPECT_EQ(0.02, coil.getDouble(insertionIndex - 1).get());
 
       // No Unitary -> IDD DEFAULTS
-      EXPECT_EQ(0.0, coil.getDouble(insertionIndex).get());       // Maximum Cycling Rate
-      EXPECT_EQ(0.0, coil.getDouble(insertionIndex + 1).get());   // Latent Capacity Time Constant
+      // E+ 23.2.0 defaults Maximum Cycling Rate and Latent Capacity Time Constant to 0.0, we don't, cf #4999
+      EXPECT_EQ(2.5, coil.getDouble(insertionIndex).get());       // Maximum Cycling Rate
+      EXPECT_EQ(60.0, coil.getDouble(insertionIndex + 1).get());  // Latent Capacity Time Constant
       EXPECT_EQ(60.0, coil.getDouble(insertionIndex + 2).get());  // Fan Delay Time
 
       EXPECT_EQ("Yes", coil.getString(insertionIndex + 3).get());