Repository: NREL/OpenStudio (language: python)

Issue:
Support "No Load Supply Air Flow Rate Ratio" on UnitarySystemPerformance:Multispeed
Enhancement Request
--------------

Currently this field neither exists in the OS IDD nor gets forward translated to IDF.



Discussion / hints:
Looks like UnitarySystemPerformance:Multispeed was introduced to E+ back in v8.4.0, at which point it did not have either the "No Load Supply Air Flow Rate Ratio" or "Single Mode Operation" fields. "Single Mode Operation" got introduced in v8.5.0 and "No Load Supply Air Flow Rate Ratio" got introduced in v8.8.0.

Fix patch:
diff --git a/resources/model/OpenStudio.idd b/resources/model/OpenStudio.idd
index 38805817a62..deb7d7d227b 100644
--- a/resources/model/OpenStudio.idd
+++ b/resources/model/OpenStudio.idd
@@ -11361,7 +11361,14 @@ OS:UnitarySystemPerformance:Multispeed,
        \note This choice does not apply to speed 1 operation.
        \note Yes = operate at the highest speed possible without exceeding the current load.
        \note No = allow operation at the average of two adjacent speeds to match the current load.
-  N1 , \field Heating Speed 1 Supply Air Flow Ratio
+  N1 , \field No Load Supply Air Flow Rate Ratio
+       \required-field
+       \type real
+       \minimum 0
+       \maximum 1
+       \note Used to define the no load operating air flow rate when the system fan
+       \note is specified to operate continuously.
+  N2 , \field Heating Speed 1 Supply Air Flow Ratio
        \required-field
        \type real
        \autosizable
@@ -11371,7 +11378,7 @@ OS:UnitarySystemPerformance:Multispeed,
        \note Enter the lowest operating supply air flow ratio during heating
        \note operation or specify autosize. This value is the ratio of air flow
        \note at this speed to the maximum air flow rate.
-  N2 ; \field Cooling Speed 1 Supply Air Flow Ratio
+  N3 ; \field Cooling Speed 1 Supply Air Flow Ratio
        \required-field
        \type real
        \autosizable
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateUnitarySystemPerformanceMultispeed.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateUnitarySystemPerformanceMultispeed.cpp
index b5c5724fdc4..b36f962849a 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateUnitarySystemPerformanceMultispeed.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateUnitarySystemPerformanceMultispeed.cpp
@@ -105,6 +105,9 @@ namespace energyplus {
     // Single mode operation
     sysPerf.setString(UnitarySystemPerformance_MultispeedFields::SingleModeOperation, modelObject.singleModeOperation() ? "Yes" : "No");
 
+    // No Load Supply Air Flow Rate Ratio
+    sysPerf.setDouble(UnitarySystemPerformance_MultispeedFields::NoLoadSupplyAirFlowRateRatio, modelObject.noLoadSupplyAirflowRateRatio());
+
     for (const auto& airflowRatioField : modelObject.supplyAirflowRatioFields()) {
       sysPerf.pushExtensibleGroup(airflowRatioField.getHeatingCoolingRatiosAsStrings());
     }
diff --git a/src/model/UnitarySystemPerformanceMultispeed.cpp b/src/model/UnitarySystemPerformanceMultispeed.cpp
index 67909d62ed7..b7a787cde2b 100644
--- a/src/model/UnitarySystemPerformanceMultispeed.cpp
+++ b/src/model/UnitarySystemPerformanceMultispeed.cpp
@@ -80,6 +80,17 @@ namespace model {
       OS_ASSERT(setString(OS_UnitarySystemPerformance_MultispeedFields::SingleModeOperation, ""));
     }
 
+    double UnitarySystemPerformanceMultispeed_Impl::noLoadSupplyAirflowRateRatio() const {
+      boost::optional<double> value = getDouble(OS_UnitarySystemPerformance_MultispeedFields::NoLoadSupplyAirFlowRateRatio, true);
+      OS_ASSERT(value);
+      return value.get();
+    }
+
+    bool UnitarySystemPerformanceMultispeed_Impl::setNoLoadSupplyAirflowRateRatio(double noLoadSupplyAirflowRateRatio) {
+      bool result = setDouble(OS_UnitarySystemPerformance_MultispeedFields::NoLoadSupplyAirFlowRateRatio, noLoadSupplyAirflowRateRatio);
+      return result;
+    }
+
     boost::optional<double> UnitarySystemPerformanceMultispeed_Impl::readDoubleFieldOrAutosize(unsigned fieldNum,
                                                                                                const ModelExtensibleGroup& group) const {
       boost::optional<double> result = group.getDouble(fieldNum);
@@ -203,6 +214,9 @@ namespace model {
   UnitarySystemPerformanceMultispeed::UnitarySystemPerformanceMultispeed(const Model& model)
     : ModelObject(UnitarySystemPerformanceMultispeed::iddObjectType(), model) {
     OS_ASSERT(getImpl<detail::UnitarySystemPerformanceMultispeed_Impl>());
+
+    bool ok = setNoLoadSupplyAirflowRateRatio(1.0);
+    OS_ASSERT(ok);
   }
 
   IddObjectType UnitarySystemPerformanceMultispeed::iddObjectType() {
@@ -221,6 +235,14 @@ namespace model {
     getImpl<detail::UnitarySystemPerformanceMultispeed_Impl>()->resetSingleModeOperation();
   }
 
+  double UnitarySystemPerformanceMultispeed::noLoadSupplyAirflowRateRatio() const {
+    return getImpl<detail::UnitarySystemPerformanceMultispeed_Impl>()->noLoadSupplyAirflowRateRatio();
+  }
+
+  bool UnitarySystemPerformanceMultispeed::setNoLoadSupplyAirflowRateRatio(double noLoadSupplyAirflowRateRatio) {
+    return getImpl<detail::UnitarySystemPerformanceMultispeed_Impl>()->setNoLoadSupplyAirflowRateRatio(noLoadSupplyAirflowRateRatio);
+  }
+
   std::vector<SupplyAirflowRatioField> UnitarySystemPerformanceMultispeed::supplyAirflowRatioFields() const {
     return getImpl<detail::UnitarySystemPerformanceMultispeed_Impl>()->supplyAirflowRatioFields();
   }
diff --git a/src/model/UnitarySystemPerformanceMultispeed.hpp b/src/model/UnitarySystemPerformanceMultispeed.hpp
index f415e144bf6..e073bd8ecb1 100644
--- a/src/model/UnitarySystemPerformanceMultispeed.hpp
+++ b/src/model/UnitarySystemPerformanceMultispeed.hpp
@@ -98,6 +98,8 @@ namespace model {
 
     bool singleModeOperation() const;
 
+    double noLoadSupplyAirflowRateRatio() const;
+
     std::vector<SupplyAirflowRatioField> supplyAirflowRatioFields() const;
 
     //@}
@@ -108,6 +110,8 @@ namespace model {
 
     void resetSingleModeOperation();
 
+    bool setNoLoadSupplyAirflowRateRatio(double noLoadSupplyAirflowRateRatio);
+
     bool setSupplyAirflowRatioFields(const std::vector<SupplyAirflowRatioField>& airflowRatioFields);
 
     bool addSupplyAirflowRatioField(const SupplyAirflowRatioField& airflowRatio);
diff --git a/src/model/UnitarySystemPerformanceMultispeed_Impl.hpp b/src/model/UnitarySystemPerformanceMultispeed_Impl.hpp
index 11d75169f57..ff7409a86db 100644
--- a/src/model/UnitarySystemPerformanceMultispeed_Impl.hpp
+++ b/src/model/UnitarySystemPerformanceMultispeed_Impl.hpp
@@ -66,6 +66,8 @@ namespace model {
       //@{
       bool singleModeOperation() const;
 
+      double noLoadSupplyAirflowRateRatio() const;
+
       std::vector<SupplyAirflowRatioField> supplyAirflowRatioFields() const;
 
       //@}
@@ -75,6 +77,8 @@ namespace model {
 
       void resetSingleModeOperation();
 
+      bool setNoLoadSupplyAirflowRateRatio(double noLoadSupplyAirflowRateRatio);
+
       bool setSupplyAirflowRatioFields(const std::vector<SupplyAirflowRatioField>& airflowRatioFields);
 
       bool addSupplyAirflowRatioField(const SupplyAirflowRatioField& airflowRatio);
diff --git a/src/osversion/VersionTranslator.cpp b/src/osversion/VersionTranslator.cpp
index 3e0e1995a28..53296f7baf6 100644
--- a/src/osversion/VersionTranslator.cpp
+++ b/src/osversion/VersionTranslator.cpp
@@ -149,7 +149,8 @@ namespace osversion {
     m_updateMethods[VersionString("3.3.0")] = &VersionTranslator::update_3_2_1_to_3_3_0;
     m_updateMethods[VersionString("3.4.0")] = &VersionTranslator::update_3_3_0_to_3_4_0;
     m_updateMethods[VersionString("3.5.0")] = &VersionTranslator::update_3_4_0_to_3_5_0;
-    m_updateMethods[VersionString("3.5.1")] = &VersionTranslator::defaultUpdate;
+    m_updateMethods[VersionString("3.5.1")] = &VersionTranslator::update_3_5_0_to_3_5_1;
+    // m_updateMethods[VersionString("3.5.1")] = &VersionTranslator::defaultUpdate;
 
     // List of previous versions that may be updated to this one.
     //   - To increment the translator, add an entry for the version just released (branched for release).
@@ -7650,9 +7651,49 @@ namespace osversion {
 
   }  // end update_3_4_0_to_3_5_0
 
-  /*   std::string VersionTranslator::update_3_5_0_to_3_5_1(const IdfFile& idf_3_5_0, const IddFileAndFactoryWrapper& idd_3_5_1) {
+  std::string VersionTranslator::update_3_5_0_to_3_5_1(const IdfFile& idf_3_5_0, const IddFileAndFactoryWrapper& idd_3_5_1) {
+    std::stringstream ss;
+    boost::optional<std::string> value;
+
+    ss << idf_3_5_0.header() << '\n' << '\n';
+    IdfFile targetIdf(idd_3_5_1.iddFile());
+    ss << targetIdf.versionObject().get();
+
+    for (const IdfObject& object : idf_3_5_0.objects()) {
+      auto iddname = object.iddObject().name();
+
+      if (iddname == "OS:UnitarySystemPerformance:Multispeed") {
+
+        // 1 Field has been added from 3.5.0 to 3.5.1:
+        // ----------------------------------------------
+        // * No Load Supply Air Flow Rate Ratio * 3
+        auto iddObject = idd_3_5_1.getObject(iddname);
+        IdfObject newObject(iddObject.get());
+
+        for (size_t i = 0; i < object.numFields(); ++i) {
+          if ((value = object.getString(i))) {
+            if (i < 3) {
+              newObject.setString(i, value.get());
+            } else {
+              newObject.setString(i + 1, value.get());
+            }
+          }
+        }
+
+        newObject.setDouble(3, 1.0);
+
+        m_refactored.push_back(RefactoredObjectData(object, newObject));
+        ss << newObject;
+
+        // No-op
+      } else {
+        ss << object;
+      }
+    }
+
+    return ss.str();
 
-  }  // end update_3_5_0_to_3_5_1 */
+  }  // end update_3_5_0_to_3_5_1
 
 }  // namespace osversion
 }  // namespace openstudio
diff --git a/src/osversion/VersionTranslator.hpp b/src/osversion/VersionTranslator.hpp
index b65bc0db7d4..5f5443d09f2 100644
--- a/src/osversion/VersionTranslator.hpp
+++ b/src/osversion/VersionTranslator.hpp
@@ -255,7 +255,7 @@ namespace osversion {
     std::string update_3_2_1_to_3_3_0(const IdfFile& idf_3_2_1, const IddFileAndFactoryWrapper& idd_3_3_0);
     std::string update_3_3_0_to_3_4_0(const IdfFile& idf_3_3_0, const IddFileAndFactoryWrapper& idd_3_4_0);
     std::string update_3_4_0_to_3_5_0(const IdfFile& idf_3_4_0, const IddFileAndFactoryWrapper& idd_3_5_0);
-    // std::string update_3_5_0_to_3_5_1(const IdfFile& idf_3_5_0, const IddFileAndFactoryWrapper& idd_3_5_1);
+    std::string update_3_5_0_to_3_5_1(const IdfFile& idf_3_5_0, const IddFileAndFactoryWrapper& idd_3_5_1);
 
     IdfObject updateUrlField_0_7_1_to_0_7_2(const IdfObject& object, unsigned index);
 


Test patch:
diff --git a/src/model/test/UnitarySystemPerformanceMultispeed_GTest.cpp b/src/model/test/UnitarySystemPerformanceMultispeed_GTest.cpp
index 8c6e86d1b0c..ad54c6660cd 100644
--- a/src/model/test/UnitarySystemPerformanceMultispeed_GTest.cpp
+++ b/src/model/test/UnitarySystemPerformanceMultispeed_GTest.cpp
@@ -66,6 +66,11 @@ TEST_F(ModelFixture, UnitarySystemPerformanceMultispeed_GetterSetters) {
   testObject.resetSingleModeOperation();
   EXPECT_FALSE(testObject.singleModeOperation());
 
+  // No Load
+  EXPECT_EQ(1.0, testObject.noLoadSupplyAirflowRateRatio());
+  EXPECT_TRUE(testObject.setNoLoadSupplyAirflowRateRatio(0.5));
+  EXPECT_EQ(0.5, testObject.noLoadSupplyAirflowRateRatio());
+
   // Supply Airflow Ratios
   EXPECT_TRUE(testObject.addSupplyAirflowRatioField(1.0, 2.0));
   EXPECT_TRUE(testObject.addSupplyAirflowRatioField(SupplyAirflowRatioField::fromCoolingRatio(3.0)));
diff --git a/src/osversion/test/3_5_1/test_vt_UnitarySystemPerformanceMultispeed.osm b/src/osversion/test/3_5_1/test_vt_UnitarySystemPerformanceMultispeed.osm
new file mode 100644
index 00000000000..f9c452dfa47
--- /dev/null
+++ b/src/osversion/test/3_5_1/test_vt_UnitarySystemPerformanceMultispeed.osm
@@ -0,0 +1,14 @@
+
+OS:Version,
+  {d7f7e956-16ea-4ccc-91a3-5e4bcf8ae550}, !- Handle
+  3.5.0;                                  !- Version Identifier
+
+OS:UnitarySystemPerformance:Multispeed,
+  {6c8ec907-01e0-45f6-b86e-7049009538d4}, !- Handle
+  Unitary System Performance Multispeed 1, !- Name
+  No,                                     !- Single Mode Operation
+  1,                                      !- Heating Speed Supply Air Flow Ratio 1
+  2,                                      !- Cooling Speed Supply Air Flow Ratio 1
+  autosize,                               !- Heating Speed Supply Air Flow Ratio 2
+  3;                                      !- Cooling Speed Supply Air Flow Ratio 2
+
diff --git a/src/osversion/test/3_5_1/test_vt_UnitarySystemPerformanceMultispeed.rb b/src/osversion/test/3_5_1/test_vt_UnitarySystemPerformanceMultispeed.rb
new file mode 100644
index 00000000000..e741956a1c0
--- /dev/null
+++ b/src/osversion/test/3_5_1/test_vt_UnitarySystemPerformanceMultispeed.rb
@@ -0,0 +1,12 @@
+#require '/usr/local/openstudio-3.5.0/Ruby/openstudio'
+
+include OpenStudio::Model
+
+m = Model.new
+
+perf = UnitarySystemPerformanceMultispeed.new(m)
+perf.setSingleModeOperation(false)
+perf.addSupplyAirflowRatioField(1.0, 2.0)
+perf.addSupplyAirflowRatioField(SupplyAirflowRatioField::fromCoolingRatio(3.0))
+
+m.save('test_vt_UnitarySystemPerformanceMultispeed.osm', true)
diff --git a/src/osversion/test/VersionTranslator_GTest.cpp b/src/osversion/test/VersionTranslator_GTest.cpp
index 69f03de46ec..e5db906dfbd 100644
--- a/src/osversion/test/VersionTranslator_GTest.cpp
+++ b/src/osversion/test/VersionTranslator_GTest.cpp
@@ -2392,3 +2392,29 @@ TEST_F(OSVersionFixture, update_3_4_0_to_3_5_0_TableMultiVariableLookup_oneDim_o
     ++i;
   }
 }
+
+TEST_F(OSVersionFixture, update_3_5_0_to_3_5_1_UnitarySystemPerformanceMultispeed) {
+  openstudio::path path = resourcesPath() / toPath("osversion/3_5_1/test_vt_UnitarySystemPerformanceMultispeed.osm");
+  osversion::VersionTranslator vt;
+  boost::optional<model::Model> model = vt.loadModel(path);
+  ASSERT_TRUE(model) << "Failed to load " << path;
+
+  openstudio::path outPath = resourcesPath() / toPath("osversion/3_5_1/test_vt_UnitarySystemPerformanceMultispeed_updated.osm");
+  model->save(outPath, true);
+
+  std::vector<WorkspaceObject> perfs = model->getObjectsByType("OS:UnitarySystemPerformance:Multispeed");
+  ASSERT_EQ(1u, perfs.size());
+  WorkspaceObject perf = perfs[0];
+
+  EXPECT_EQ("Unitary System Performance Multispeed 1", perf.nameString());
+  EXPECT_EQ("No", perf.getString(2).get());  // OS_UnitarySystemPerformance_MultispeedFields::SingleModeOperation
+  EXPECT_EQ(1.0, perf.getDouble(3).get());   // OS_UnitarySystemPerformance_MultispeedFields::NoLoadSupplyAirFlowRateRatio
+
+  EXPECT_EQ(2u, perf.numExtensibleGroups());
+  auto eg1 = perf.extensibleGroups()[0];
+  EXPECT_EQ(1.0, eg1.getDouble(0, false).get());
+  EXPECT_EQ(2.0, eg1.getDouble(1, false).get());
+  auto eg2 = perf.extensibleGroups()[1];
+  EXPECT_EQ("autosize", eg2.getString(0, false).get());
+  EXPECT_EQ(3.0, eg2.getDouble(1, false).get());
+}