Repository: NREL/OpenStudio (language: python)

Issue:
Wrap E+ 22.2 new supplemental heating coil type for `AirLoopHVAC:UnitarySystem` object
Enhancement Request
--------------
EnergyPlus has been improved to allow multi-staging backup coil by adding `Coil:Heating:Electric:Multistage` coil type enumeration to `Supplemental Heating Coil Object Type` field for `AirLoopHVAC:UnitarySystem` object, see [EnergyPlus PR being merged](https://github.com/NREL/EnergyPlus/pull/9414). This improvement will be included in the upcoming E+ release, OpenStudio needs to be updated to coordinate this change.


Wrap E+ 22.2 new supplemental heating coil type for `AirLoopHVAC:UnitarySystem` object
Enhancement Request
--------------
EnergyPlus has been improved to allow multi-staging backup coil by adding `Coil:Heating:Electric:Multistage` coil type enumeration to `Supplemental Heating Coil Object Type` field for `AirLoopHVAC:UnitarySystem` object, see [EnergyPlus PR being merged](https://github.com/NREL/EnergyPlus/pull/9414). This improvement will be included in the upcoming E+ release, OpenStudio needs to be updated to coordinate this change.


multi speed DX coil stage mapping does not clone properly with airloop
Issue overview
--------------

## Current Behavior

When I clone an airloop with multi speed DX coils using the OS API, the coils themselves are cloned properly, as well as their corresponding stages (dx multi speed stage data objects). But the stage assignments do not map over (the stage fields in the coil object are blank)

## Expected Behavior

It should work.

## Steps to Reproduce

Let me know what details you need to investigate. I don't think it makes sense to attach a model will blank fields as evidence. It wouldn't really show anything...

## Possible Solution
<!--- Optional, but if you can, suggest a fix/reason for the bug, -->

## Details

### Environment

Some additional details about your environment for this issue (if relevant):
 - Version of OpenStudio (if using an intermediate build, include SHA): 3.4.0


Discussion / hints:
@joseph-robertson Is creating this issue sufficient to ensure the feature will be included in the next release?
This requires that `Coil:Heating:Electric:MultiStage` be wrapped, which might be pretty straightforward - we have already-wrapped `Coil:Heating:Gas:MultiStage` to follow as a guide.
@joseph-robertson Is creating this issue sufficient to ensure the feature will be included in the next release?
This requires that `Coil:Heating:Electric:MultiStage` be wrapped, which might be pretty straightforward - we have already-wrapped `Coil:Heating:Gas:MultiStage` to follow as a guide.


Fix patch:
diff --git a/developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_4_1_TBD.md b/developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_4_1_TBD.md
index 2440ccf9f5e..80fa128c3a2 100644
--- a/developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_4_1_TBD.md
+++ b/developer/doc/ReleaseNotes/OpenStudio_Release_Notes_3_4_1_TBD.md
@@ -82,6 +82,8 @@ You can also refer to the [OpenStudio SDK Python Binding Version Compatibility M
 * [#4665](https://github.com/NREL/OpenStudio/pull/4665) - Deprecate methods on Coil DX objects
     * `Coil:Cooling:DX:SingleSpeed`, `Coil:Cooling:DX:MultiSpeed:StageData`, and `Coil:Cooling:DX:CurveFit:Speed`: `ratedEvaporatorFanPowerPerVolumeFlowRate` and `setRatedEvaporatorFanPowerPerVolumeFlowRate` in favor of `ratedEvaporatorFanPowerPerVolumeFlowRate2017` and `setRatedEvaporatorFanPowerPerVolumeFlowRate2017`
     * `Coil:Heating:DX:SingleSpeed` and `Coil:Heating:DX:MultiSpeed:StageData`: `ratedSupplyFanPowerPerVolumeFlowRate` and `setRatedSupplyFanPowerPerVolumeFlowRate` in favor of `ratedSupplyFanPowerPerVolumeFlowRate2017` and `setRatedSupplyFanPowerPerVolumeFlowRate2017`
+* [#4666](https://github.com/NREL/OpenStudio/pull/4666) - Changes related to availability schedule methods
+    * `Coil:Heating:Gas:MultiStage` has an API-breaking change related to its `availabilitySchedule` getter. It is now a required field that returns `Schedule` instead of `boost::optional<Schedule>`. Method `resetAvailabilitySchedule` is also removed.     
 
 
 ## Minor changes and bug fixes
diff --git a/resources/energyplus/ProposedEnergy+.idd b/resources/energyplus/ProposedEnergy+.idd
index 08d96ac653d..763b4a94053 100644
--- a/resources/energyplus/ProposedEnergy+.idd
+++ b/resources/energyplus/ProposedEnergy+.idd
@@ -31800,6 +31800,9 @@ Coil:Heating:Electric:MultiStage,
        \memo within another component such as an air terminal unit, zone HVAC equipment, or unitary
        \memo system, then the coil is controlled by the parent component and the setpoint node name
        \memo is not entered.
+       \min-fields 6
+       \max-fields 14
+       \extensible:2
   A1 , \field Name
        \type alpha
        \required-field
@@ -31828,42 +31831,16 @@ Coil:Heating:Electric:MultiStage,
        \note capacity and Efficiency.
   N2, \field Stage 1 Efficiency
        \required-field
+       \begin-extensible
        \type real
        \units W/W
        \minimum> 0.0
-  N3, \field Stage 1 Nominal Capacity
+  N3; \field Stage 1 Nominal Capacity
        \required-field
        \type real
        \units W
        \minimum> 0.0
        \autosizable
-  N4, \field Stage 2 Efficiency
-       \type real
-       \units W/W
-       \minimum> 0.0
-  N5, \field Stage 2 Nominal Capacity
-       \type real
-       \units W
-       \minimum> 0.0
-       \autosizable
-  N6, \field Stage 3 Efficiency
-       \type real
-       \units W/W
-       \minimum> 0.0
-  N7, \field Stage 3 Nominal Capacity
-       \type real
-       \units W
-       \minimum> 0.0
-       \autosizable
-  N8, \field Stage 4 Efficiency
-       \type real
-       \units W/W
-       \minimum> 0.0
-  N9; \field Stage 4 Nominal Capacity
-       \type real
-       \units W
-       \minimum> 0.0
-       \autosizable
 
 Coil:Heating:Fuel,
        \memo Gas or other fuel heating coil. If the coil is located directly in an air loop branch or
@@ -31943,6 +31920,7 @@ Coil:Heating:Gas:MultiStage,
        \memo system, then the coil is controlled by the parent component and the setpoint node name
        \memo is not entered.
        \min-fields 8
+       \max-fields 20
        \extensible:3
   A1 , \field Name
        \required-field
diff --git a/resources/model/OpenStudio.idd b/resources/model/OpenStudio.idd
index 9780bec1791..585c54fe38e 100644
--- a/resources/model/OpenStudio.idd
+++ b/resources/model/OpenStudio.idd
@@ -11096,6 +11096,7 @@ OS:AirLoopHVAC:UnitarySystem,
   A16, \field Supplemental Heating Coil Name
        \type object-list
        \object-list HeatingCoilName
+       \object-list HeatingCoilsElectricMultiStage
        \object-list HeatingCoilsDesuperheater
        \object-list UserDefinedCoil
        \note Enter the name of the supplemental heating coil if included in the unitary system.
@@ -17991,6 +17992,63 @@ OS:Coil:Heating:Electric,
        \type object-list
        \object-list Node
 
+OS:Coil:Heating:Electric:MultiStage,
+       \memo Electric heating coil, multi-stage. If the coil is located directly in an air loop
+       \memo branch or outdoor air equipment list, then it is controlled on leaving air temperature
+       \memo and the Temperature Setpoint Node Name must be specified. If the coil is contained
+       \memo within another component such as an air terminal unit, zone HVAC equipment, or unitary
+       \memo system, then the coil is controlled by the parent component and the setpoint node name
+       \memo is not entered.
+       \max-fields 9
+       \extensible:1
+  A1,  \field Handle
+       \type handle
+       \required-field
+  A2 , \field Name
+       \type alpha
+       \required-field
+       \reference HeatingCoilsElectricMultiStage
+       \reference ConnectionObject
+  A3 , \field Availability Schedule
+       \note Availability schedule name for this system. Schedule value > 0 means the system is available.
+       \note If this field is blank, the system is always available.
+       \type object-list
+       \object-list ScheduleNames
+       \required-field
+  A4 , \field Air Inlet Node
+       \type object-list
+       \required-field
+       \object-list ConnectionNames
+  A5 , \field Air Outlet Node
+       \type object-list
+       \required-field
+       \object-list ConnectionNames
+  A7;  \field Stage 1
+       \begin-extensible
+       \type object-list
+       \object-list CoilHeatingElectricMultiStageStageData
+
+OS:Coil:Heating:Electric:MultiStage:StageData,
+       \min-fields 4
+  A1,  \field Handle
+       \type handle
+       \required-field
+  A2 , \field Name
+       \required-field
+       \reference CoilHeatingElectricMultiStageStageData
+       \type alpha
+  N1,  \field Efficiency
+       \required-field
+       \type real
+       \units W/W
+       \minimum> 0.0
+  N2;  \field Nominal Capacity
+       \required-field
+       \type real
+       \units W
+       \minimum> 0.0
+       \autosizable
+
 OS:Coil:Heating:Gas,
        \min-fields 1
   A1, \field Handle
@@ -18065,7 +18123,14 @@ OS:Coil:Heating:Gas,
        \default NaturalGas
 
 OS:Coil:Heating:Gas:MultiStage,
-       \extensible:1
+      \memo Gas heating coil, multi-stage. If the coil is located directly in an air loop
+      \memo branch or outdoor air equipment list, then it is controlled on leaving air temperature
+      \memo and the Temperature Setpoint Node Name must be specified. If the coil is contained
+      \memo within another component such as an air terminal unit, zone HVAC equipment, or unitary
+      \memo system, then the coil is controlled by the parent component and the setpoint node name
+      \memo is not entered.
+      \max-fields 11
+      \extensible:1
   A1, \field Handle
       \type handle
       \required-field
@@ -18077,6 +18142,7 @@ OS:Coil:Heating:Gas:MultiStage,
   A3, \field Availability Schedule
       \type object-list
       \object-list ScheduleNames
+      \required-field
   A4, \field Air Inlet Node
       \type object-list
       \required-field
@@ -18096,7 +18162,8 @@ OS:Coil:Heating:Gas:MultiStage,
       \object-list CoilHeatingGasMultiStageStageData
 
 OS:Coil:Heating:Gas:MultiStage:StageData,
-  A1, \field Handle
+       \min-fields 5
+  A1,  \field Handle
        \type handle
        \required-field
   A2 , \field Name
diff --git a/src/energyplus/CMakeLists.txt b/src/energyplus/CMakeLists.txt
index 07ff6512576..fd59a2aa401 100644
--- a/src/energyplus/CMakeLists.txt
+++ b/src/energyplus/CMakeLists.txt
@@ -84,6 +84,7 @@ set(${target_name}_src
   ForwardTranslator/ForwardTranslateCoilHeatingDXVariableRefrigerantFlow.cpp
   ForwardTranslator/ForwardTranslateCoilHeatingDXVariableSpeed.cpp
   ForwardTranslator/ForwardTranslateCoilHeatingElectric.cpp
+  ForwardTranslator/ForwardTranslateCoilHeatingElectricMultiStage.cpp
   ForwardTranslator/ForwardTranslateCoilHeatingGas.cpp
   ForwardTranslator/ForwardTranslateCoilHeatingGasMultiStage.cpp
   ForwardTranslator/ForwardTranslateCoilHeatingWater.cpp
@@ -633,6 +634,7 @@ set(${target_name}_test_src
   Test/AirflowNetwork_GTest.cpp
 
   Test/AirLoopHVACUnitarySystem_GTest.cpp
+  Test/AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_GTest.cpp
   Test/AirLoopHVACUnitaryHeatCoolVAVChangeoverBypass_GTest.cpp
   Test/AirTerminalDualDuctConstantVolume_GTest.cpp
   Test/AirTerminalDualDuctVAV_GTest.cpp
@@ -670,7 +672,9 @@ set(${target_name}_test_src
   Test/CoilCoolingDXTwoStageWithHumidityControlMode_GTest.cpp
   Test/CoilCoolingDXVariableSpeed_GTest.cpp
   Test/CoilHeatingDXMultiSpeed_GTest.cpp
+  Test/CoilHeatingElectricMultiStage_GTest.cpp
   Test/CoilHeatingGas_GTest.cpp
+  Test/CoilHeatingGasMultiStage_GTest.cpp
   Test/CoilSystemIntegratedHeatPumpAirSource_GTest.cpp
   Test/CoilWaterHeatingAirToWaterHeatPumpVariableSpeed_GTest.cpp
   Test/CoilWaterHeatingDesuperheater_GTest.cpp
diff --git a/src/energyplus/ForwardTranslator.cpp b/src/energyplus/ForwardTranslator.cpp
index 0e81b5f7e41..68e4a676bd2 100644
--- a/src/energyplus/ForwardTranslator.cpp
+++ b/src/energyplus/ForwardTranslator.cpp
@@ -1122,6 +1122,15 @@ namespace energyplus {
         retVal = translateCoilHeatingElectric(coil);
         break;
       }
+      case openstudio::IddObjectType::OS_Coil_Heating_Electric_MultiStage: {
+        model::CoilHeatingElectricMultiStage coil = modelObject.cast<CoilHeatingElectricMultiStage>();
+        retVal = translateCoilHeatingElectricMultiStage(coil);
+        break;
+      }
+      case openstudio::IddObjectType::OS_Coil_Heating_Electric_MultiStage_StageData: {
+        // no-op
+        return retVal;
+      }
       case openstudio::IddObjectType::OS_Coil_Heating_Gas: {
         model::CoilHeatingGas coil = modelObject.cast<CoilHeatingGas>();
         retVal = translateCoilHeatingGas(coil);
diff --git a/src/energyplus/ForwardTranslator.hpp b/src/energyplus/ForwardTranslator.hpp
index 3fcd95345bd..7661517ea21 100644
--- a/src/energyplus/ForwardTranslator.hpp
+++ b/src/energyplus/ForwardTranslator.hpp
@@ -143,6 +143,7 @@ namespace model {
   class CoilHeatingDXVariableRefrigerantFlow;
   class CoilHeatingDXVariableSpeed;
   class CoilHeatingElectric;
+  class CoilHeatingElectricMultiStage;
   class CoilHeatingGas;
   class CoilHeatingGasMultiStage;
   class CoilHeatingWater;
@@ -808,6 +809,8 @@ namespace energyplus {
 
     boost::optional<IdfObject> translateCoilHeatingElectric(model::CoilHeatingElectric& modelObject);
 
+    boost::optional<IdfObject> translateCoilHeatingElectricMultiStage(model::CoilHeatingElectricMultiStage& modelObject);
+
     boost::optional<IdfObject> translateCoilHeatingGas(model::CoilHeatingGas& modelObject);
 
     boost::optional<IdfObject> translateCoilHeatingGasMultiStage(model::CoilHeatingGasMultiStage& modelObject);
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateAirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateAirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.cpp
index a15eae7afcf..624589eb783 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateAirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateAirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.cpp
@@ -46,6 +46,7 @@
 #include <utilities/idd/Coil_Heating_DX_SingleSpeed_FieldEnums.hxx>
 #include <utilities/idd/Coil_Heating_Fuel_FieldEnums.hxx>
 #include <utilities/idd/Coil_Heating_Gas_MultiStage_FieldEnums.hxx>
+#include <utilities/idd/Coil_Heating_Electric_MultiStage_FieldEnums.hxx>
 #include <utilities/idd/Coil_Heating_Electric_FieldEnums.hxx>
 #include <utilities/idd/Coil_Heating_Water_FieldEnums.hxx>
 #include <utilities/idd/Coil_Heating_DX_MultiSpeed_FieldEnums.hxx>
@@ -345,6 +346,9 @@ namespace energyplus {
       if (_heatingCoil->iddObject().type() == IddObjectType::Coil_Heating_Gas_MultiStage) {
         _heatingCoil->setString(Coil_Heating_Gas_MultiStageFields::AirInletNodeName, heatingCoilInletNodeName);
         _heatingCoil->setString(Coil_Heating_Gas_MultiStageFields::AirOutletNodeName, heatingCoilOutletNodeName);
+      } else if (_heatingCoil->iddObject().type() == IddObjectType::Coil_Heating_Electric_MultiStage) {
+        _heatingCoil->setString(Coil_Heating_Electric_MultiStageFields::AirInletNodeName, heatingCoilInletNodeName);
+        _heatingCoil->setString(Coil_Heating_Electric_MultiStageFields::AirOutletNodeName, heatingCoilOutletNodeName);
       } else if (_heatingCoil->iddObject().type() == IddObjectType::Coil_Heating_Water) {
         _heatingCoil->setString(Coil_Heating_WaterFields::AirInletNodeName, heatingCoilInletNodeName);
         _heatingCoil->setString(Coil_Heating_WaterFields::AirOutletNodeName, heatingCoilOutletNodeName);
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateAirLoopHVACUnitarySystem.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateAirLoopHVACUnitarySystem.cpp
index ee3db1e039a..71905d1acee 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateAirLoopHVACUnitarySystem.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateAirLoopHVACUnitarySystem.cpp
@@ -91,6 +91,7 @@
 #include <utilities/idd/Coil_Heating_DX_SingleSpeed_FieldEnums.hxx>
 #include <utilities/idd/Coil_Heating_DX_VariableSpeed_FieldEnums.hxx>
 #include <utilities/idd/Coil_Heating_Electric_FieldEnums.hxx>
+#include <utilities/idd/Coil_Heating_Electric_MultiStage_FieldEnums.hxx>
 #include <utilities/idd/Coil_Heating_Gas_MultiStage_FieldEnums.hxx>
 #include <utilities/idd/Coil_Heating_Fuel_FieldEnums.hxx>
 #include <utilities/idd/Coil_Heating_Water_FieldEnums.hxx>
@@ -775,6 +776,9 @@ namespace energyplus {
       } else if (_heatingCoil->iddObject().type() == IddObjectType::Coil_Heating_Electric) {
         _heatingCoil->setString(Coil_Heating_ElectricFields::AirInletNodeName, inletNodeName);
         _heatingCoil->setString(Coil_Heating_ElectricFields::AirOutletNodeName, outletNodeName);
+      } else if (_heatingCoil->iddObject().type() == IddObjectType::Coil_Heating_Electric_MultiStage) {
+        _heatingCoil->setString(Coil_Heating_Electric_MultiStageFields::AirInletNodeName, inletNodeName);
+        _heatingCoil->setString(Coil_Heating_Electric_MultiStageFields::AirOutletNodeName, outletNodeName);
       } else if (_heatingCoil->iddObject().type() == IddObjectType::Coil_Heating_Fuel) {
         _heatingCoil->setString(Coil_Heating_FuelFields::AirInletNodeName, inletNodeName);
         _heatingCoil->setString(Coil_Heating_FuelFields::AirOutletNodeName, outletNodeName);
@@ -822,6 +826,9 @@ namespace energyplus {
       if (_supplementalHeatingCoil->iddObject().type() == IddObjectType::Coil_Heating_Electric) {
         _supplementalHeatingCoil->setString(Coil_Heating_ElectricFields::AirInletNodeName, inletNodeName);
         _supplementalHeatingCoil->setString(Coil_Heating_ElectricFields::AirOutletNodeName, airOutletNodeName.get());
+      } else if (_supplementalHeatingCoil->iddObject().type() == IddObjectType::Coil_Heating_Electric_MultiStage) {
+        _supplementalHeatingCoil->setString(Coil_Heating_Electric_MultiStageFields::AirInletNodeName, inletNodeName);
+        _supplementalHeatingCoil->setString(Coil_Heating_Electric_MultiStageFields::AirOutletNodeName, airOutletNodeName.get());
       } else if (_supplementalHeatingCoil->iddObject().type() == IddObjectType::Coil_Heating_Fuel) {
         _supplementalHeatingCoil->setString(Coil_Heating_FuelFields::AirInletNodeName, inletNodeName);
         _supplementalHeatingCoil->setString(Coil_Heating_FuelFields::AirOutletNodeName, airOutletNodeName.get());
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateCoilHeatingElectricMultiStage.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateCoilHeatingElectricMultiStage.cpp
new file mode 100644
index 00000000000..68442efca46
--- /dev/null
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateCoilHeatingElectricMultiStage.cpp
@@ -0,0 +1,105 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include "../ForwardTranslator.hpp"
+#include "../../model/Model.hpp"
+#include "../../model/CoilHeatingElectricMultiStage.hpp"
+#include "../../model/CoilHeatingElectricMultiStage_Impl.hpp"
+#include "../../model/CoilHeatingElectricMultiStageStageData.hpp"
+#include "../../model/CoilHeatingElectricMultiStageStageData_Impl.hpp"
+#include "../../model/Node.hpp"
+#include "../../model/Node_Impl.hpp"
+#include "../../model/Schedule.hpp"
+#include "../../model/Schedule_Impl.hpp"
+#include <utilities/idd/Coil_Heating_Electric_MultiStage_FieldEnums.hxx>
+#include "../../utilities/idd/IddEnums.hpp"
+#include <utilities/idd/IddEnums.hxx>
+#include <utilities/idd/IddFactory.hxx>
+#include "../../utilities/idf/IdfExtensibleGroup.hpp"
+
+using namespace openstudio::model;
+
+namespace openstudio {
+
+namespace energyplus {
+
+  boost::optional<IdfObject> ForwardTranslator::translateCoilHeatingElectricMultiStage(CoilHeatingElectricMultiStage& modelObject) {
+    boost::optional<std::string> s;
+    boost::optional<double> value;
+
+    // Name
+    IdfObject idfObject = createRegisterAndNameIdfObject(openstudio::IddObjectType::Coil_Heating_Electric_MultiStage, modelObject);
+
+    // AvailabilityScheduleName
+    {
+      auto schedule = modelObject.availabilitySchedule();
+      if (boost::optional<IdfObject> _schedule = translateAndMapModelObject(schedule)) {
+        idfObject.setString(Coil_Heating_Electric_MultiStageFields::AvailabilityScheduleName, _schedule->name().get());
+      }
+    }
+
+    // AirInletNodeName
+    if (auto node = modelObject.inletModelObject()) {
+      idfObject.setString(Coil_Heating_Electric_MultiStageFields::AirInletNodeName, node->name().get());
+    }
+
+    // AirOutletNodeName
+    // TemperatureSetpointNodeName
+    if (auto node = modelObject.outletModelObject()) {
+      idfObject.setString(Coil_Heating_Electric_MultiStageFields::AirOutletNodeName, node->name().get());
+      idfObject.setString(Coil_Heating_Electric_MultiStageFields::TemperatureSetpointNodeName, node->name().get());
+    }
+
+    const auto stages = modelObject.stages();
+
+    // NumberofStages
+    if (!stages.empty()) {
+      idfObject.setInt(Coil_Heating_Electric_MultiStageFields::NumberofStages, stages.size());
+    }
+
+    for (const auto& stage : stages) {
+      auto eg = idfObject.pushExtensibleGroup();
+
+      // Stage1Efficiency
+      eg.setDouble(Coil_Heating_Electric_MultiStageExtensibleFields::StageEfficiency, stage.efficiency());
+
+      // Stage1NominalCapacity
+      if (stage.isNominalCapacityAutosized()) {
+        eg.setString(Coil_Heating_Electric_MultiStageExtensibleFields::StageNominalCapacity, "AutoSize");
+      } else if ((value = stage.nominalCapacity())) {
+        eg.setDouble(Coil_Heating_Electric_MultiStageExtensibleFields::StageNominalCapacity, value.get());
+      }
+    }
+
+    return idfObject;
+  }
+
+}  // namespace energyplus
+
+}  // namespace openstudio
diff --git a/src/energyplus/ForwardTranslator/ForwardTranslateCoilHeatingGasMultiStage.cpp b/src/energyplus/ForwardTranslator/ForwardTranslateCoilHeatingGasMultiStage.cpp
index a49dfb3f5c7..34853d57ee8 100644
--- a/src/energyplus/ForwardTranslator/ForwardTranslateCoilHeatingGasMultiStage.cpp
+++ b/src/energyplus/ForwardTranslator/ForwardTranslateCoilHeatingGasMultiStage.cpp
@@ -59,8 +59,9 @@ namespace energyplus {
     IdfObject idfObject = createRegisterAndNameIdfObject(openstudio::IddObjectType::Coil_Heating_Gas_MultiStage, modelObject);
 
     // AvailabilityScheduleName
-    if (auto schedule = modelObject.availabilitySchedule()) {
-      if (auto _schedule = translateAndMapModelObject(schedule.get())) {
+    {
+      auto schedule = modelObject.availabilitySchedule();
+      if (boost::optional<IdfObject> _schedule = translateAndMapModelObject(schedule)) {
         idfObject.setString(Coil_Heating_Gas_MultiStageFields::AvailabilityScheduleName, _schedule->name().get());
       }
     }
@@ -89,19 +90,18 @@ namespace energyplus {
       idfObject.setDouble(Coil_Heating_Gas_MultiStageFields::ParasiticGasLoad, value.get());
     }
 
+    const auto stages = modelObject.stages();
+
     // NumberofStages
-    {
-      auto num = modelObject.stages().size();
-      idfObject.setInt(Coil_Heating_Gas_MultiStageFields::NumberofStages, num);
+    if (!stages.empty()) {
+      idfObject.setInt(Coil_Heating_Gas_MultiStageFields::NumberofStages, stages.size());
     }
 
-    for (auto stage : modelObject.stages()) {
+    for (const auto& stage : stages) {
       auto eg = idfObject.pushExtensibleGroup();
 
       // Stage1GasBurnerEfficiency
-      if ((value = stage.gasBurnerEfficiency())) {
-        eg.setDouble(Coil_Heating_Gas_MultiStageExtensibleFields::StageGasBurnerEfficiency, value.get());
-      }
+      eg.setDouble(Coil_Heating_Gas_MultiStageExtensibleFields::StageGasBurnerEfficiency, stage.gasBurnerEfficiency());
 
       // Stage1NominalCapacity
       if (stage.isNominalCapacityAutosized()) {
@@ -111,9 +111,7 @@ namespace energyplus {
       }
 
       // Stage1ParasiticElectricLoad
-      if ((value = stage.parasiticElectricLoad())) {
-        eg.setDouble(Coil_Heating_Gas_MultiStageExtensibleFields::StageParasiticElectricLoad, value.get());
-      }
+      eg.setDouble(Coil_Heating_Gas_MultiStageExtensibleFields::StageParasiticElectricLoad, stage.parasiticElectricLoad());
     }
 
     return idfObject;
diff --git a/src/energyplus/Test/AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_GTest.cpp b/src/energyplus/Test/AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_GTest.cpp
new file mode 100644
index 00000000000..f2d5ebc41a7
--- /dev/null
+++ b/src/energyplus/Test/AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_GTest.cpp
@@ -0,0 +1,106 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include <gtest/gtest.h>
+#include "EnergyPlusFixture.hpp"
+
+#include "../ForwardTranslator.hpp"
+
+#include "../../model/Model.hpp"
+#include "../../model/AirLoopHVAC.hpp"
+#include "../../model/AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.hpp"
+#include "../../model/Node.hpp"
+
+#include "../../model/CoilCoolingDXMultiSpeed.hpp"
+#include "../../model/FanConstantVolume.hpp"
+#include "../../model/CoilHeatingGasMultiStage.hpp"
+#include "../../model/CoilHeatingElectric.hpp"
+#include "../../model/CoilHeatingElectricMultiStage.hpp"
+
+#include "../../utilities/idf/IdfObject.hpp"
+#include "../../utilities/idf/IdfExtensibleGroup.hpp"
+
+#include <utilities/idd/AirLoopHVAC_UnitarySystem_FieldEnums.hxx>
+#include <utilities/idd/Coil_Cooling_DX_SingleSpeed_FieldEnums.hxx>
+#include <utilities/idd/Fan_ConstantVolume_FieldEnums.hxx>
+#include <utilities/idd/Coil_Heating_Gas_MultiStage_FieldEnums.hxx>
+#include <utilities/idd/Coil_Heating_Electric_MultiStage_FieldEnums.hxx>
+#include <utilities/idd/IddEnums.hxx>
+
+#include <resources.hxx>
+
+#include <sstream>
+
+using namespace openstudio::energyplus;
+using namespace openstudio::model;
+using namespace openstudio;
+
+TEST_F(EnergyPlusFixture, ForwardTranslator_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_CoilHeatingGasMultiStage) {
+  Model m;
+
+  CoilCoolingDXMultiSpeed c(m);
+  CoilHeatingGasMultiStage h(m);
+  FanConstantVolume f(m);
+  CoilHeatingElectric s(m);
+
+  AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed unitary(m, f, h, c, s);
+
+  AirLoopHVAC airLoop(m);
+
+  Node supplyOutletNode = airLoop.supplyOutletNode();
+  unitary.addToNode(supplyOutletNode);
+
+  ForwardTranslator ft;
+  Workspace workspace = ft.translateModel(m);
+
+  EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeed).size());
+  EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::Coil_Heating_Gas_MultiStage).size());
+}
+
+TEST_F(EnergyPlusFixture, ForwardTranslator_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_CoilHeatingElectricMultiStage) {
+  Model m;
+
+  CoilCoolingDXMultiSpeed c(m);
+  CoilHeatingElectricMultiStage h(m);
+  FanConstantVolume f(m);
+  CoilHeatingElectric s(m);
+
+  AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed unitary(m, f, h, c, s);
+
+  AirLoopHVAC airLoop(m);
+
+  Node supplyOutletNode = airLoop.supplyOutletNode();
+  unitary.addToNode(supplyOutletNode);
+
+  ForwardTranslator ft;
+  Workspace workspace = ft.translateModel(m);
+
+  EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeed).size());
+  EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::Coil_Heating_Electric_MultiStage).size());
+}
diff --git a/src/energyplus/Test/AirLoopHVACUnitarySystem_GTest.cpp b/src/energyplus/Test/AirLoopHVACUnitarySystem_GTest.cpp
index 2a1f424fb6d..0cc82caedbf 100644
--- a/src/energyplus/Test/AirLoopHVACUnitarySystem_GTest.cpp
+++ b/src/energyplus/Test/AirLoopHVACUnitarySystem_GTest.cpp
@@ -41,6 +41,8 @@
 #include "../../model/CoilHeatingDXSingleSpeed.hpp"
 #include "../../model/FanConstantVolume.hpp"
 #include "../../model/CoilHeatingDesuperheater.hpp"
+#include "../../model/CoilHeatingGasMultiStage.hpp"
+#include "../../model/CoilHeatingElectricMultiStage.hpp"
 
 #include "../../utilities/idf/IdfObject.hpp"
 #include "../../utilities/idf/IdfExtensibleGroup.hpp"
@@ -50,6 +52,8 @@
 #include <utilities/idd/Coil_Heating_DX_SingleSpeed_FieldEnums.hxx>
 #include <utilities/idd/Fan_ConstantVolume_FieldEnums.hxx>
 #include <utilities/idd/Coil_Heating_Desuperheater_FieldEnums.hxx>
+#include <utilities/idd/Coil_Heating_Gas_MultiStage_FieldEnums.hxx>
+#include <utilities/idd/Coil_Heating_Electric_MultiStage_FieldEnums.hxx>
 #include <utilities/idd/IddEnums.hxx>
 
 #include <resources.hxx>
@@ -657,3 +661,53 @@ TEST_F(EnergyPlusFixture, ForwardTranslator_AirLoopHVACUnitarySystem_Nodes) {
     }
   }
 }
+
+TEST_F(EnergyPlusFixture, ForwardTranslator_AirLoopHVACUnitarySystem_CoilHeatingGasMultiStage) {
+  Model m;
+
+  CoilCoolingDXSingleSpeed c(m);
+  CoilHeatingGasMultiStage h(m);
+  FanConstantVolume f(m);
+
+  AirLoopHVACUnitarySystem unitary(m);
+  unitary.setCoolingCoil(c);
+  unitary.setHeatingCoil(h);
+  unitary.setSupplyFan(f);
+
+  AirLoopHVAC airLoop(m);
+
+  Node supplyOutletNode = airLoop.supplyOutletNode();
+  unitary.addToNode(supplyOutletNode);
+
+  ForwardTranslator ft;
+  Workspace workspace = ft.translateModel(m);
+
+  EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitarySystem).size());
+  EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::Coil_Heating_Gas_MultiStage).size());
+}
+
+TEST_F(EnergyPlusFixture, ForwardTranslator_AirLoopHVACUnitarySystem_CoilHeatingElectricMultiStage) {
+  Model m;
+
+  CoilCoolingDXSingleSpeed c(m);
+  CoilHeatingElectricMultiStage h(m);
+  FanConstantVolume f(m);
+  CoilHeatingElectricMultiStage s(m);
+
+  AirLoopHVACUnitarySystem unitary(m);
+  unitary.setCoolingCoil(c);
+  unitary.setHeatingCoil(h);
+  unitary.setSupplyFan(f);
+  unitary.setSupplementalHeatingCoil(s);
+
+  AirLoopHVAC airLoop(m);
+
+  Node supplyOutletNode = airLoop.supplyOutletNode();
+  unitary.addToNode(supplyOutletNode);
+
+  ForwardTranslator ft;
+  Workspace workspace = ft.translateModel(m);
+
+  EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitarySystem).size());
+  EXPECT_EQ(2u, workspace.getObjectsByType(IddObjectType::Coil_Heating_Electric_MultiStage).size());
+}
diff --git a/src/energyplus/Test/CoilHeatingElectricMultiStage_GTest.cpp b/src/energyplus/Test/CoilHeatingElectricMultiStage_GTest.cpp
new file mode 100644
index 00000000000..5800a587d05
--- /dev/null
+++ b/src/energyplus/Test/CoilHeatingElectricMultiStage_GTest.cpp
@@ -0,0 +1,261 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include <gtest/gtest.h>
+#include "EnergyPlusFixture.hpp"
+
+#include "../ForwardTranslator.hpp"
+
+#include "../../model/Model.hpp"
+#include "../../model/AirLoopHVAC.hpp"
+#include "../../model/AirLoopHVACUnitarySystem.hpp"
+#include "../../model/AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.hpp"
+#include "../../model/Node.hpp"
+#include "../../model/CoilHeatingElectricMultiStage.hpp"
+#include "../../model/CoilHeatingElectricMultiStage_Impl.hpp"
+#include "../../model/CoilHeatingElectricMultiStageStageData.hpp"
+#include "../../model/CoilHeatingElectricMultiStageStageData_Impl.hpp"
+#include "../../model/CoilHeatingDXSingleSpeed.hpp"
+#include "../../model/CoilCoolingDXSingleSpeed.hpp"
+#include "../../model/CoilCoolingDXMultiSpeed.hpp"
+#include "../../model/CoilHeatingElectric.hpp"
+#include "../../model/FanConstantVolume.hpp"
+#include "../../model/Schedule.hpp"
+#include "../../model/Schedule_Impl.hpp"
+
+#include "../../utilities/idf/IdfObject.hpp"
+#include "../../utilities/idf/IdfExtensibleGroup.hpp"
+#include "../../utilities/idf/WorkspaceObject.hpp"
+#include "../../utilities/idf/WorkspaceExtensibleGroup.hpp"
+
+#include <utilities/idd/Coil_Heating_Electric_MultiStage_FieldEnums.hxx>
+#include <utilities/idd/AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeed_FieldEnums.hxx>
+#include <utilities/idd/AirLoopHVAC_UnitarySystem_FieldEnums.hxx>
+#include <utilities/idd/IddEnums.hxx>
+
+using namespace openstudio::energyplus;
+using namespace openstudio::model;
+using namespace openstudio;
+
+TEST_F(EnergyPlusFixture, ForwardTranslator_CoilHeatingElectricMultiStage_HeatingCoil) {
+  {  // AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed
+    Model m;
+
+    CoilCoolingDXMultiSpeed c(m);
+    c.setName("HP CC");
+    CoilHeatingElectricMultiStage h(m);
+    h.setName("HP HC");
+    FanConstantVolume f(m);
+    f.setName("HP FanConstantVol");
+    CoilHeatingElectric s(m);
+    s.setName("HP SupHC");
+
+    auto alwaysOn = m.alwaysOnDiscreteSchedule();
+    alwaysOn.setName("HP HC AvailSch");
+    h.setAvailabilitySchedule(alwaysOn);
+
+    CoilHeatingElectricMultiStageStageData stageData(m);
+    stageData.setEfficiency(1.5);
+    stageData.setNominalCapacity(1000.0);
+    h.addStage(stageData);
+
+    AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed unitary(m, f, h, c, s);
+    unitary.setName("UnitarySys");
+
+    AirLoopHVAC airLoop(m);
+    Node supplyOutletNode = airLoop.supplyOutletNode();
+    unitary.addToNode(supplyOutletNode);
+
+    ForwardTranslator ft;
+    Workspace workspace = ft.translateModel(m);
+
+    EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeed).size());
+    EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::Coil_Heating_Electric_MultiStage).size());
+
+    IdfObject idf_hp = workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeed)[0];
+    IdfObject idf_coil = workspace.getObjectsByType(IddObjectType::Coil_Heating_Electric_MultiStage)[0];
+
+    EXPECT_EQ("HP FanConstantVol", idf_hp.getString(AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeedFields::SupplyAirFanName, false).get());
+    EXPECT_EQ("Coil:Heating:Electric:MultiStage",
+              idf_hp.getString(AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeedFields::HeatingCoilObjectType, false).get());
+    EXPECT_EQ("HP HC", idf_hp.getString(AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeedFields::HeatingCoilName, false).get());
+    EXPECT_EQ("HP CC", idf_hp.getString(AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeedFields::CoolingCoilName, false).get());
+    EXPECT_EQ("HP SupHC", idf_hp.getString(AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeedFields::SupplementalHeatingCoilName, false).get());
+
+    EXPECT_EQ("HP HC AvailSch", idf_coil.getString(Coil_Heating_Electric_MultiStageFields::AvailabilityScheduleName, false).get());
+    EXPECT_EQ("UnitarySys Cool Coil Outlet", idf_coil.getString(Coil_Heating_Electric_MultiStageFields::AirInletNodeName, false).get());
+    EXPECT_EQ("UnitarySys Heat Coil Outlet", idf_coil.getString(Coil_Heating_Electric_MultiStageFields::AirOutletNodeName, false).get());
+    EXPECT_TRUE(idf_coil.isEmpty(Coil_Heating_Electric_MultiStageFields::TemperatureSetpointNodeName));
+    EXPECT_EQ(1, idf_coil.getInt(Coil_Heating_Electric_MultiStageFields::NumberofStages, false).get());
+    ASSERT_EQ(1, idf_coil.numExtensibleGroups());
+    auto egs = idf_coil.extensibleGroups();
+    auto stages = h.stages();
+    for (size_t i = 0; i < idf_coil.extensibleGroups().size(); ++i) {
+      const IdfExtensibleGroup& eg = egs[i];
+      const auto stage = stages[i];
+
+      EXPECT_EQ(1.5, eg.getDouble(Coil_Heating_Electric_MultiStageExtensibleFields::StageEfficiency).get());
+      EXPECT_EQ(1000.0, eg.getDouble(Coil_Heating_Electric_MultiStageExtensibleFields::StageNominalCapacity).get());
+    }
+  }
+
+  {  // AirLoopHVACUnitarySystem
+    Model m;
+
+    CoilCoolingDXSingleSpeed c(m);
+    c.setName("HP CC");
+    CoilHeatingElectricMultiStage h(m);
+    h.setName("HP HC");
+    FanConstantVolume f(m);
+    f.setName("HP FanConstantVol");
+    CoilHeatingElectric s(m);
+    s.setName("HP SupHC");
+
+    auto alwaysOn = m.alwaysOnDiscreteSchedule();
+    alwaysOn.setName("HP HC AvailSch");
+    h.setAvailabilitySchedule(alwaysOn);
+
+    CoilHeatingElectricMultiStageStageData stageData(m);
+    stageData.setEfficiency(1.5);
+    stageData.setNominalCapacity(1000.0);
+    h.addStage(stageData);
+
+    AirLoopHVACUnitarySystem unitary(m);
+    unitary.setName("UnitarySys");
+    EXPECT_TRUE(unitary.setCoolingCoil(c));
+    EXPECT_TRUE(unitary.setHeatingCoil(h));
+    EXPECT_TRUE(unitary.setSupplyFan(f));
+    EXPECT_TRUE(unitary.setSupplementalHeatingCoil(s));
+
+    AirLoopHVAC airLoop(m);
+    Node supplyOutletNode = airLoop.supplyOutletNode();
+    unitary.addToNode(supplyOutletNode);
+
+    ForwardTranslator ft;
+    Workspace workspace = ft.translateModel(m);
+
+    EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitarySystem).size());
+    EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::Coil_Heating_Electric_MultiStage).size());
+
+    IdfObject idf_unitary = workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitarySystem)[0];
+    IdfObject idf_coil = workspace.getObjectsByType(IddObjectType::Coil_Heating_Electric_MultiStage)[0];
+
+    EXPECT_EQ("HP FanConstantVol", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::SupplyFanName, false).get());
+    EXPECT_EQ("Coil:Heating:Electric:MultiStage", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::HeatingCoilObjectType, false).get());
+    EXPECT_EQ("HP HC", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::HeatingCoilName, false).get());
+    EXPECT_EQ("HP CC", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::CoolingCoilName, false).get());
+    EXPECT_EQ("HP SupHC", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::SupplementalHeatingCoilName, false).get());
+
+    EXPECT_EQ("HP HC AvailSch", idf_coil.getString(Coil_Heating_Electric_MultiStageFields::AvailabilityScheduleName, false).get());
+    EXPECT_EQ("UnitarySys Cooling Coil - Heating Coil Node",
+              idf_coil.getString(Coil_Heating_Electric_MultiStageFields::AirInletNodeName, false).get());
+    EXPECT_EQ("UnitarySys Heating Coil - Fan Node", idf_coil.getString(Coil_Heating_Electric_MultiStageFields::AirOutletNodeName, false).get());
+    EXPECT_TRUE(idf_coil.isEmpty(Coil_Heating_Electric_MultiStageFields::TemperatureSetpointNodeName));
+    EXPECT_EQ(1, idf_coil.getInt(Coil_Heating_Electric_MultiStageFields::NumberofStages, false).get());
+    ASSERT_EQ(1, idf_coil.numExtensibleGroups());
+    auto egs = idf_coil.extensibleGroups();
+    auto stages = h.stages();
+    for (size_t i = 0; i < idf_coil.extensibleGroups().size(); ++i) {
+      const IdfExtensibleGroup& eg = egs[i];
+      const auto stage = stages[i];
+
+      EXPECT_EQ(1.5, eg.getDouble(Coil_Heating_Electric_MultiStageExtensibleFields::StageEfficiency).get());
+      EXPECT_EQ(1000.0, eg.getDouble(Coil_Heating_Electric_MultiStageExtensibleFields::StageNominalCapacity).get());
+    }
+  }
+}
+
+TEST_F(EnergyPlusFixture, ForwardTranslator_CoilHeatingElectricMultiStage_SuppHeatingCoil) {
+  Model m;
+
+  CoilCoolingDXSingleSpeed c(m);
+  c.setName("HP CC");
+  CoilHeatingDXSingleSpeed h(m);
+  h.setName("HP HC");
+  FanConstantVolume f(m);
+  f.setName("HP FanConstantVol");
+  CoilHeatingElectricMultiStage s(m);
+  s.setName("HP SupHC");
+
+  auto alwaysOn = m.alwaysOnDiscreteSchedule();
+  alwaysOn.setName("HP SupHC AvailSch");
+  s.setAvailabilitySchedule(alwaysOn);
+
+  CoilHeatingElectricMultiStageStageData stageData(m);
+  stageData.setEfficiency(1.5);
+  stageData.setNominalCapacity(1000.0);
+  s.addStage(stageData);
+
+  AirLoopHVACUnitarySystem unitary(m);
+  unitary.setName("UnitarySys");
+  EXPECT_TRUE(unitary.setCoolingCoil(c));
+  EXPECT_TRUE(unitary.setHeatingCoil(h));
+  EXPECT_TRUE(unitary.setSupplyFan(f));
+  EXPECT_TRUE(unitary.setSupplementalHeatingCoil(s));
+
+  AirLoopHVAC airLoop(m);
+  Node supplyOutletNode = airLoop.supplyOutletNode();
+  unitary.addToNode(supplyOutletNode);
+  ASSERT_TRUE(unitary.inletNode());
+  EXPECT_TRUE(unitary.inletNode()->setName("UnitarySys Inlet Node"));
+  ASSERT_TRUE(unitary.outletNode());
+  EXPECT_TRUE(unitary.outletNode()->setName("UnitarySys Outlet Node"));
+
+  ForwardTranslator ft;
+  Workspace workspace = ft.translateModel(m);
+
+  EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitarySystem).size());
+  EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::Coil_Heating_Electric_MultiStage).size());
+
+  IdfObject idf_unitary = workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitarySystem)[0];
+  IdfObject idf_coil = workspace.getObjectsByType(IddObjectType::Coil_Heating_Electric_MultiStage)[0];
+
+  EXPECT_EQ("HP FanConstantVol", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::SupplyFanName, false).get());
+  EXPECT_EQ("HP HC", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::HeatingCoilName, false).get());
+  EXPECT_EQ("HP CC", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::CoolingCoilName, false).get());
+  EXPECT_EQ("Coil:Heating:Electric:MultiStage",
+            idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::SupplementalHeatingCoilObjectType, false).get());
+  EXPECT_EQ("HP SupHC", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::SupplementalHeatingCoilName, false).get());
+
+  EXPECT_EQ("HP SupHC AvailSch", idf_coil.getString(Coil_Heating_Electric_MultiStageFields::AvailabilityScheduleName, false).get());
+  EXPECT_EQ("UnitarySys Fan - Supplemental Coil Node", idf_coil.getString(Coil_Heating_Electric_MultiStageFields::AirInletNodeName, false).get());
+  EXPECT_EQ("UnitarySys Outlet Node", idf_coil.getString(Coil_Heating_Electric_MultiStageFields::AirOutletNodeName, false).get());
+  EXPECT_TRUE(idf_coil.isEmpty(Coil_Heating_Electric_MultiStageFields::TemperatureSetpointNodeName));
+  EXPECT_EQ(1, idf_coil.getInt(Coil_Heating_Electric_MultiStageFields::NumberofStages, false).get());
+  ASSERT_EQ(1, idf_coil.numExtensibleGroups());
+  auto egs = idf_coil.extensibleGroups();
+  auto stages = s.stages();
+  for (size_t i = 0; i < idf_coil.extensibleGroups().size(); ++i) {
+    const IdfExtensibleGroup& eg = egs[i];
+    const auto stage = stages[i];
+
+    EXPECT_EQ(1.5, eg.getDouble(Coil_Heating_Electric_MultiStageExtensibleFields::StageEfficiency).get());
+    EXPECT_EQ(1000.0, eg.getDouble(Coil_Heating_Electric_MultiStageExtensibleFields::StageNominalCapacity).get());
+  }
+}
diff --git a/src/energyplus/Test/CoilHeatingGasMultiStage_GTest.cpp b/src/energyplus/Test/CoilHeatingGasMultiStage_GTest.cpp
new file mode 100644
index 00000000000..2848386e4fa
--- /dev/null
+++ b/src/energyplus/Test/CoilHeatingGasMultiStage_GTest.cpp
@@ -0,0 +1,205 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include <gtest/gtest.h>
+#include "EnergyPlusFixture.hpp"
+
+#include "../ForwardTranslator.hpp"
+
+#include "../../model/Model.hpp"
+#include "../../model/AirLoopHVAC.hpp"
+#include "../../model/AirLoopHVACUnitarySystem.hpp"
+#include "../../model/AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.hpp"
+#include "../../model/Node.hpp"
+#include "../../model/CoilHeatingGasMultiStage.hpp"
+#include "../../model/CoilHeatingGasMultiStage_Impl.hpp"
+#include "../../model/CoilHeatingGasMultiStageStageData.hpp"
+#include "../../model/CoilHeatingGasMultiStageStageData_Impl.hpp"
+#include "../../model/CoilCoolingDXSingleSpeed.hpp"
+#include "../../model/CoilCoolingDXMultiSpeed.hpp"
+#include "../../model/CoilHeatingElectric.hpp"
+#include "../../model/FanConstantVolume.hpp"
+#include "../../model/CurveQuadratic.hpp"
+#include "../../model/CurveQuadratic_Impl.hpp"
+#include "../../model/Schedule.hpp"
+#include "../../model/Schedule_Impl.hpp"
+
+#include "../../utilities/idf/IdfObject.hpp"
+#include "../../utilities/idf/IdfExtensibleGroup.hpp"
+#include "../../utilities/idf/WorkspaceObject.hpp"
+#include "../../utilities/idf/WorkspaceExtensibleGroup.hpp"
+
+#include <utilities/idd/Coil_Heating_Gas_MultiStage_FieldEnums.hxx>
+#include <utilities/idd/AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeed_FieldEnums.hxx>
+#include <utilities/idd/AirLoopHVAC_UnitarySystem_FieldEnums.hxx>
+#include <utilities/idd/IddEnums.hxx>
+
+using namespace openstudio::energyplus;
+using namespace openstudio::model;
+using namespace openstudio;
+
+TEST_F(EnergyPlusFixture, ForwardTranslator_CoilHeatingGasMultiStage_HeatingCoil) {
+  {  // AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed
+    Model m;
+
+    CoilCoolingDXMultiSpeed c(m);
+    c.setName("HP CC");
+    CoilHeatingGasMultiStage h(m);
+    h.setName("HP HC");
+    FanConstantVolume f(m);
+    f.setName("HP FanConstantVol");
+    CoilHeatingElectric s(m);
+    s.setName("HP SupHC");
+
+    auto alwaysOn = m.alwaysOnDiscreteSchedule();
+    alwaysOn.setName("HP HC AvailSch");
+    h.setAvailabilitySchedule(alwaysOn);
+    CurveQuadratic curve(m);
+    curve.setName("HP HC Curve");
+    h.setPartLoadFractionCorrelationCurve(curve);
+    h.setParasiticGasLoad(10.0);
+
+    CoilHeatingGasMultiStageStageData stageData(m);
+    stageData.setGasBurnerEfficiency(2.5);
+    stageData.setNominalCapacity(2000.0);
+    stageData.setParasiticElectricLoad(15.0);
+    h.addStage(stageData);
+
+    AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed unitary(m, f, h, c, s);
+    unitary.setName("UnitarySys");
+
+    AirLoopHVAC airLoop(m);
+    Node supplyOutletNode = airLoop.supplyOutletNode();
+    unitary.addToNode(supplyOutletNode);
+
+    ForwardTranslator ft;
+    Workspace workspace = ft.translateModel(m);
+
+    EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeed).size());
+    EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::Coil_Heating_Gas_MultiStage).size());
+
+    IdfObject idf_hp = workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeed)[0];
+    IdfObject idf_coil = workspace.getObjectsByType(IddObjectType::Coil_Heating_Gas_MultiStage)[0];
+
+    EXPECT_EQ("HP FanConstantVol", idf_hp.getString(AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeedFields::SupplyAirFanName, false).get());
+    EXPECT_EQ("Coil:Heating:Gas:MultiStage",
+              idf_hp.getString(AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeedFields::HeatingCoilObjectType, false).get());
+    EXPECT_EQ("HP HC", idf_hp.getString(AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeedFields::HeatingCoilName, false).get());
+    EXPECT_EQ("HP CC", idf_hp.getString(AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeedFields::CoolingCoilName, false).get());
+    EXPECT_EQ("HP SupHC", idf_hp.getString(AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeedFields::SupplementalHeatingCoilName, false).get());
+
+    EXPECT_EQ("HP HC AvailSch", idf_coil.getString(Coil_Heating_Gas_MultiStageFields::AvailabilityScheduleName, false).get());
+    EXPECT_EQ("UnitarySys Cool Coil Outlet", idf_coil.getString(Coil_Heating_Gas_MultiStageFields::AirInletNodeName, false).get());
+    EXPECT_EQ("UnitarySys Heat Coil Outlet", idf_coil.getString(Coil_Heating_Gas_MultiStageFields::AirOutletNodeName, false).get());
+    EXPECT_TRUE(idf_coil.isEmpty(Coil_Heating_Gas_MultiStageFields::TemperatureSetpointNodeName));
+    EXPECT_EQ("HP HC Curve", idf_coil.getString(Coil_Heating_Gas_MultiStageFields::PartLoadFractionCorrelationCurveName, false).get());
+    EXPECT_EQ(10.0, idf_coil.getDouble(Coil_Heating_Gas_MultiStageFields::ParasiticGasLoad, false).get());
+    EXPECT_EQ(1, idf_coil.getInt(Coil_Heating_Gas_MultiStageFields::NumberofStages, false).get());
+    ASSERT_EQ(1, idf_coil.numExtensibleGroups());
+    auto egs = idf_coil.extensibleGroups();
+    auto stages = h.stages();
+    for (size_t i = 0; i < idf_coil.extensibleGroups().size(); ++i) {
+      const IdfExtensibleGroup& eg = egs[i];
+      const auto stage = stages[i];
+
+      EXPECT_EQ(2.5, eg.getDouble(Coil_Heating_Gas_MultiStageExtensibleFields::StageGasBurnerEfficiency).get());
+      EXPECT_EQ(2000.0, eg.getDouble(Coil_Heating_Gas_MultiStageExtensibleFields::StageNominalCapacity).get());
+      EXPECT_EQ(15.0, eg.getDouble(Coil_Heating_Gas_MultiStageExtensibleFields::StageParasiticElectricLoad).get());
+    }
+  }
+
+  {  // AirLoopHVACUnitarySystem
+    Model m;
+
+    CoilCoolingDXSingleSpeed c(m);
+    c.setName("HP CC");
+    CoilHeatingGasMultiStage h(m);
+    h.setName("HP HC");
+    FanConstantVolume f(m);
+    f.setName("HP FanConstantVol");
+
+    auto alwaysOn = m.alwaysOnDiscreteSchedule();
+    alwaysOn.setName("HP HC AvailSch");
+    h.setAvailabilitySchedule(alwaysOn);
+    CurveQuadratic curve(m);
+    curve.setName("HP HC Curve");
+    h.setPartLoadFractionCorrelationCurve(curve);
+    h.setParasiticGasLoad(10.0);
+
+    CoilHeatingGasMultiStageStageData stageData(m);
+    stageData.setGasBurnerEfficiency(2.5);
+    stageData.setNominalCapacity(2000.0);
+    stageData.setParasiticElectricLoad(15.0);
+    h.addStage(stageData);
+
+    AirLoopHVACUnitarySystem unitary(m);
+    unitary.setName("UnitarySys");
+    EXPECT_TRUE(unitary.setCoolingCoil(c));
+    EXPECT_TRUE(unitary.setHeatingCoil(h));
+    EXPECT_TRUE(unitary.setSupplyFan(f));
+
+    AirLoopHVAC airLoop(m);
+    Node supplyOutletNode = airLoop.supplyOutletNode();
+    unitary.addToNode(supplyOutletNode);
+
+    ForwardTranslator ft;
+    Workspace workspace = ft.translateModel(m);
+
+    EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitarySystem).size());
+    EXPECT_EQ(1u, workspace.getObjectsByType(IddObjectType::Coil_Heating_Gas_MultiStage).size());
+
+    IdfObject idf_unitary = workspace.getObjectsByType(IddObjectType::AirLoopHVAC_UnitarySystem)[0];
+    IdfObject idf_coil = workspace.getObjectsByType(IddObjectType::Coil_Heating_Gas_MultiStage)[0];
+
+    EXPECT_EQ("HP FanConstantVol", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::SupplyFanName, false).get());
+    EXPECT_EQ("Coil:Heating:Gas:MultiStage", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::HeatingCoilObjectType, false).get());
+    EXPECT_EQ("HP HC", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::HeatingCoilName, false).get());
+    EXPECT_EQ("HP CC", idf_unitary.getString(AirLoopHVAC_UnitarySystemFields::CoolingCoilName, false).get());
+    EXPECT_TRUE(idf_unitary.isEmpty(AirLoopHVAC_UnitarySystemFields::SupplementalHeatingCoilName));
+
+    EXPECT_EQ("HP HC AvailSch", idf_coil.getString(Coil_Heating_Gas_MultiStageFields::AvailabilityScheduleName, false).get());
+    EXPECT_EQ("UnitarySys Cooling Coil - Heating Coil Node", idf_coil.getString(Coil_Heating_Gas_MultiStageFields::AirInletNodeName, false).get());
+    EXPECT_EQ("UnitarySys Heating Coil - Fan Node", idf_coil.getString(Coil_Heating_Gas_MultiStageFields::AirOutletNodeName, false).get());
+    EXPECT_TRUE(idf_coil.isEmpty(Coil_Heating_Gas_MultiStageFields::TemperatureSetpointNodeName));
+    EXPECT_EQ("HP HC Curve", idf_coil.getString(Coil_Heating_Gas_MultiStageFields::PartLoadFractionCorrelationCurveName, false).get());
+    EXPECT_EQ(10.0, idf_coil.getDouble(Coil_Heating_Gas_MultiStageFields::ParasiticGasLoad, false).get());
+    EXPECT_EQ(1, idf_coil.getInt(Coil_Heating_Gas_MultiStageFields::NumberofStages, false).get());
+    ASSERT_EQ(1, idf_coil.numExtensibleGroups());
+    auto egs = idf_coil.extensibleGroups();
+    auto stages = h.stages();
+    for (size_t i = 0; i < idf_coil.extensibleGroups().size(); ++i) {
+      const IdfExtensibleGroup& eg = egs[i];
+      const auto stage = stages[i];
+
+      EXPECT_EQ(2.5, eg.getDouble(Coil_Heating_Gas_MultiStageExtensibleFields::StageGasBurnerEfficiency).get());
+      EXPECT_EQ(2000.0, eg.getDouble(Coil_Heating_Gas_MultiStageExtensibleFields::StageNominalCapacity).get());
+      EXPECT_EQ(15.0, eg.getDouble(Coil_Heating_Gas_MultiStageExtensibleFields::StageParasiticElectricLoad).get());
+    }
+  }
+}
diff --git a/src/model/CMakeLists.txt b/src/model/CMakeLists.txt
index 30e15e8c4b3..121dfd00bb4 100644
--- a/src/model/CMakeLists.txt
+++ b/src/model/CMakeLists.txt
@@ -374,6 +374,12 @@ set(${target_name}_src
   CoilHeatingElectric.hpp
   CoilHeatingElectric_Impl.hpp
   CoilHeatingElectric.cpp
+  CoilHeatingElectricMultiStage.hpp
+  CoilHeatingElectricMultiStage_Impl.hpp
+  CoilHeatingElectricMultiStage.cpp
+  CoilHeatingElectricMultiStageStageData.hpp
+  CoilHeatingElectricMultiStageStageData_Impl.hpp
+  CoilHeatingElectricMultiStageStageData.cpp
   CoilHeatingFourPipeBeam.hpp
   CoilHeatingFourPipeBeam_Impl.hpp
   CoilHeatingFourPipeBeam.cpp
@@ -1921,6 +1927,8 @@ set(${target_name}_test_src
   test/CoilHeatingDXVariableSpeed_GTest.cpp
   test/CoilHeatingDXVariableSpeedSpeedData_GTest.cpp
   test/CoilHeatingElectric_GTest.cpp
+  test/CoilHeatingElectricMultiStage_GTest.cpp
+  test/CoilHeatingElectricMultiStageStageData_GTest.cpp
   test/CoilHeatingFourPipeBeam_GTest.cpp
   test/CoilHeatingGas_GTest.cpp
   test/CoilHeatingGasMultiStage_GTest.cpp
diff --git a/src/model/CoilCoolingDXMultiSpeedStageData.hpp b/src/model/CoilCoolingDXMultiSpeedStageData.hpp
index a0210b875c7..6a1bc41cb9c 100644
--- a/src/model/CoilCoolingDXMultiSpeedStageData.hpp
+++ b/src/model/CoilCoolingDXMultiSpeedStageData.hpp
@@ -47,7 +47,7 @@ namespace model {
 
   }  // namespace detail
 
-  /** CoilCoolingDXMultiSpeedStageData is a ModelObject that wraps the OpenStudio IDD object 'OS:Coil:Cooling:DX:MultiSpeed:StageData'. */
+  /** CoilCoolingDXMultiSpeedStageData is a ParentObject that wraps the OpenStudio IDD object 'OS:Coil:Cooling:DX:MultiSpeed:StageData'. */
   class MODEL_API CoilCoolingDXMultiSpeedStageData : public ParentObject
   {
    public:
diff --git a/src/model/CoilCoolingDXMultiSpeedStageData_Impl.hpp b/src/model/CoilCoolingDXMultiSpeedStageData_Impl.hpp
index 71c7c64a1ec..bd2978d6571 100644
--- a/src/model/CoilCoolingDXMultiSpeedStageData_Impl.hpp
+++ b/src/model/CoilCoolingDXMultiSpeedStageData_Impl.hpp
@@ -41,7 +41,7 @@ namespace model {
 
   namespace detail {
 
-    /** CoilCoolingDXMultiSpeedStageData_Impl is a ModelObject_Impl that is the implementation class for CoilCoolingDXMultiSpeedStageData.*/
+    /** CoilCoolingDXMultiSpeedStageData_Impl is a ParentObject_Impl that is the implementation class for CoilCoolingDXMultiSpeedStageData.*/
     class MODEL_API CoilCoolingDXMultiSpeedStageData_Impl : public ParentObject_Impl
     {
      public:
diff --git a/src/model/CoilHeatingElectricMultiStage.cpp b/src/model/CoilHeatingElectricMultiStage.cpp
new file mode 100644
index 00000000000..0c5af9ca7dc
--- /dev/null
+++ b/src/model/CoilHeatingElectricMultiStage.cpp
@@ -0,0 +1,373 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include "Model.hpp"
+#include "Model_Impl.hpp"
+#include "CoilHeatingElectricMultiStage.hpp"
+#include "CoilHeatingElectricMultiStage_Impl.hpp"
+#include "Schedule.hpp"
+#include "Schedule_Impl.hpp"
+#include "CoilHeatingElectricMultiStageStageData.hpp"
+#include "CoilHeatingElectricMultiStageStageData_Impl.hpp"
+#include "AirLoopHVACUnitarySystem.hpp"
+#include "AirLoopHVACUnitarySystem_Impl.hpp"
+#include "AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.hpp"
+#include "AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_Impl.hpp"
+#include "ScheduleTypeLimits.hpp"
+#include "ScheduleTypeRegistry.hpp"
+
+#include <utilities/idd/OS_Coil_Heating_Electric_MultiStage_FieldEnums.hxx>
+#include <utilities/idd/IddEnums.hxx>
+#include "../utilities/units/Unit.hpp"
+#include "../utilities/core/Assert.hpp"
+#include "../utilities/idf/WorkspaceExtensibleGroup.hpp"
+
+namespace openstudio {
+namespace model {
+
+  namespace detail {
+
+    CoilHeatingElectricMultiStage_Impl::CoilHeatingElectricMultiStage_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle)
+      : StraightComponent_Impl(idfObject, model, keepHandle) {
+      OS_ASSERT(idfObject.iddObject().type() == CoilHeatingElectricMultiStage::iddObjectType());
+    }
+
+    CoilHeatingElectricMultiStage_Impl::CoilHeatingElectricMultiStage_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model,
+                                                                           bool keepHandle)
+      : StraightComponent_Impl(other, model, keepHandle) {
+      OS_ASSERT(other.iddObject().type() == CoilHeatingElectricMultiStage::iddObjectType());
+    }
+
+    CoilHeatingElectricMultiStage_Impl::CoilHeatingElectricMultiStage_Impl(const CoilHeatingElectricMultiStage_Impl& other, Model_Impl* model,
+                                                                           bool keepHandle)
+      : StraightComponent_Impl(other, model, keepHandle) {}
+
+    const std::vector<std::string>& CoilHeatingElectricMultiStage_Impl::outputVariableNames() const {
+      static const std::vector<std::string> result{"Heating Coil Heating Energy", "Heating Coil Heating Rate", "Heating Coil Electric Consumption",
+                                                   "Heating Coil Electricity Rate"};
+      return result;
+    }
+
+    IddObjectType CoilHeatingElectricMultiStage_Impl::iddObjectType() const {
+      return CoilHeatingElectricMultiStage::iddObjectType();
+    }
+
+    std::vector<ScheduleTypeKey> CoilHeatingElectricMultiStage_Impl::getScheduleTypeKeys(const Schedule& schedule) const {
+      std::vector<ScheduleTypeKey> result;
+      UnsignedVector fieldIndices = getSourceIndices(schedule.handle());
+      UnsignedVector::const_iterator b(fieldIndices.begin());
+      UnsignedVector::const_iterator e(fieldIndices.end());
+      if (std::find(b, e, OS_Coil_Heating_Electric_MultiStageFields::AvailabilitySchedule) != e) {
+        result.emplace_back("CoilHeatingElectricMultiStage", "Availability Schedule");
+      }
+      return result;
+    }
+
+    unsigned CoilHeatingElectricMultiStage_Impl::inletPort() const {
+      return OS_Coil_Heating_Electric_MultiStageFields::AirInletNode;
+    }
+
+    unsigned CoilHeatingElectricMultiStage_Impl::outletPort() const {
+      return OS_Coil_Heating_Electric_MultiStageFields::AirOutletNode;
+    }
+
+    ModelObject CoilHeatingElectricMultiStage_Impl::clone(Model model) const {
+      auto t_clone = StraightComponent_Impl::clone(model).cast<CoilHeatingElectricMultiStage>();
+
+      // Deal with the stages: we want them cloned, so first clear and then clone + assign each
+      t_clone.removeAllStages();
+
+      for (const auto& stage : stages()) {
+        auto stageClone = stage.clone(model).cast<CoilHeatingElectricMultiStageStageData>();
+        t_clone.addStage(stageClone);
+      }
+      return t_clone;
+    }
+
+    std::vector<ModelObject> CoilHeatingElectricMultiStage_Impl::children() const {
+      return subsetCastVector<ModelObject>(stages());
+    }
+
+    boost::optional<HVACComponent> CoilHeatingElectricMultiStage_Impl::containingHVACComponent() const {
+      // AirLoopHVACUnitarySystem
+      std::vector<AirLoopHVACUnitarySystem> airLoopHVACUnitarySystems = this->model().getConcreteModelObjects<AirLoopHVACUnitarySystem>();
+
+      for (const auto& airLoopHVACUnitarySystem : airLoopHVACUnitarySystems) {
+        if (boost::optional<HVACComponent> heatingCoil = airLoopHVACUnitarySystem.heatingCoil()) {
+          if (heatingCoil->handle() == this->handle()) {
+            return airLoopHVACUnitarySystem;
+          }
+        }
+        if (boost::optional<HVACComponent> suppHeatingCoil = airLoopHVACUnitarySystem.supplementalHeatingCoil()) {
+          if (suppHeatingCoil->handle() == this->handle()) {
+            return airLoopHVACUnitarySystem;
+          }
+        }
+      }
+
+      // AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed
+      std::vector<AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed> airLoopHVACUnitaryHeatPumpAirToAirMultiSpeeds =
+        this->model().getConcreteModelObjects<AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed>();
+
+      for (const auto& airLoopHVACUnitaryHeatPumpAirToAirMultiSpeed : airLoopHVACUnitaryHeatPumpAirToAirMultiSpeeds) {
+        if (boost::optional<HVACComponent> heatingCoil = airLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.heatingCoil()) {
+          if (heatingCoil->handle() == this->handle()) {
+            return airLoopHVACUnitaryHeatPumpAirToAirMultiSpeed;
+          }
+        }
+      }
+
+      return boost::none;
+    }
+
+    bool CoilHeatingElectricMultiStage_Impl::addToNode(Node& /*node*/) {
+      return false;
+    }
+
+    boost::optional<Schedule> CoilHeatingElectricMultiStage_Impl::optionalAvailabilitySchedule() const {
+      return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_Coil_Heating_Electric_MultiStageFields::AvailabilitySchedule);
+    }
+
+    Schedule CoilHeatingElectricMultiStage_Impl::availabilitySchedule() const {
+      boost::optional<Schedule> value = optionalAvailabilitySchedule();
+      if (!value) {
+        // it is an error if we get here, however we don't want to crash
+        // so we hook up to global always on schedule
+        LOG(Error, "Required availability schedule not set, using 'Always On' schedule");
+        value = this->model().alwaysOnDiscreteSchedule();
+        OS_ASSERT(value);
+        const_cast<CoilHeatingElectricMultiStage_Impl*>(this)->setAvailabilitySchedule(*value);
+        value = optionalAvailabilitySchedule();
+      }
+      OS_ASSERT(value);
+      return value.get();
+    }
+
+    bool CoilHeatingElectricMultiStage_Impl::setAvailabilitySchedule(Schedule& schedule) {
+      bool result = setSchedule(OS_Coil_Heating_Electric_MultiStageFields::AvailabilitySchedule, "CoilHeatingElectricMultiStage",
+                                "Availability Schedule", schedule);
+      return result;
+    }
+
+    unsigned CoilHeatingElectricMultiStage_Impl::numberOfStages() const {
+      return numExtensibleGroups();
+    }
+
+    std::vector<CoilHeatingElectricMultiStageStageData> CoilHeatingElectricMultiStage_Impl::stages() const {
+      std::vector<CoilHeatingElectricMultiStageStageData> result;
+      for (const auto& group : extensibleGroups()) {
+        if (auto target = group.cast<WorkspaceExtensibleGroup>().getTarget(OS_Coil_Heating_Electric_MultiStageExtensibleFields::Stage)) {
+          if (auto stage = target->optionalCast<CoilHeatingElectricMultiStageStageData>()) {
+            result.push_back(stage.get());
+          }
+        }
+      }
+      return result;
+    }
+
+    boost::optional<unsigned> CoilHeatingElectricMultiStage_Impl::stageIndex(const CoilHeatingElectricMultiStageStageData& stage) const {
+
+      boost::optional<unsigned> result;
+
+      auto egs = castVector<WorkspaceExtensibleGroup>(extensibleGroups());
+      auto h = openstudio::toString(stage.handle());
+      auto it = std::find_if(egs.begin(), egs.end(), [&](const WorkspaceExtensibleGroup& eg) {
+        return (eg.getField(OS_Coil_Heating_Electric_MultiStageExtensibleFields::Stage).get() == h);
+      });
+
+      // If found, we compute the index by using std::distance between the start of vector and the iterator returned by std::find_if
+      if (it != egs.end()) {
+        result = std::distance(egs.begin(), it) + 1;
+      }
+
+      return result;
+    }
+
+    bool CoilHeatingElectricMultiStage_Impl::addStage(const CoilHeatingElectricMultiStageStageData& stage) {
+      if (auto _c = stage.parentCoil()) {
+        if (this->handle() == _c->handle()) {
+          return true;  // already the case
+        } else {
+          LOG(Error, "For " << briefDescription() << " cannot add " << stage.briefDescription()
+                            << " since this Stage is already in use by another coil ('" << _c->nameString() << "').");
+          return false;
+        }
+      }
+      auto group = getObject<ModelObject>().pushExtensibleGroup().cast<WorkspaceExtensibleGroup>();
+      if (group.empty()) {
+        LOG(Error, "You have reached the maximum number of stages (=" << numberOfStages() << "), occurred for " << briefDescription() << ".");
+        return false;
+      }
+      bool result = group.setPointer(OS_Coil_Heating_Electric_MultiStageExtensibleFields::Stage, stage.handle());
+      if (!result) {
+        // Something went wrong, so erase the new extensible group
+        getObject<ModelObject>().eraseExtensibleGroup(group.groupIndex());
+      }
+      return result;
+    }
+
+    bool CoilHeatingElectricMultiStage_Impl::setStageIndex(const CoilHeatingElectricMultiStageStageData& stage, unsigned index) {
+      boost::optional<unsigned> idx = stageIndex(stage);
+      if (!idx) {
+        LOG(Warn, "For " << briefDescription() << " cannot set the index of stage " << stage.briefDescription() << " since it is not part of it.");
+        return false;
+      }
+
+      // TODO: we could just set via string instead of doing a ton of typechecking below...
+
+      std::vector<CoilHeatingElectricMultiStageStageData> stageVector = stages();
+
+      if (index > stageVector.size()) {
+        LOG(Warn, "Requested a stage index of " << index << " to be assigned to " << stage.briefDescription() << ", but " << briefDescription()
+                                                << " only has " << stageVector.size() << " stages, resetting to that.");
+        index = stageVector.size();
+      } else if (index < 1) {
+        LOG(Warn, "Requested a stage index of " << index << " < 1 to be assigned to " << stage.briefDescription() << ", resetting to 1");
+        index = 1;
+      }
+
+      stageVector.erase(stageVector.begin() + idx.get() - 1);  // stageIndex is 1-indexed, and vector is 0-indexed
+
+      stageVector.insert(stageVector.begin() + (index - 1), stage);
+
+      return setStages(stageVector);
+    }
+
+    bool CoilHeatingElectricMultiStage_Impl::addStage(const CoilHeatingElectricMultiStageStageData& stage, unsigned index) {
+      bool ok = addStage(stage);
+      if (!ok) {
+        return false;
+      }
+      ok = setStageIndex(stage, index);
+      return ok;
+    }
+
+    bool CoilHeatingElectricMultiStage_Impl::setStages(const std::vector<CoilHeatingElectricMultiStageStageData>& stages) {
+      // Clear the extensible groups, and redo them
+      bool ok = true;
+      clearExtensibleGroups();
+      for (const CoilHeatingElectricMultiStageStageData& s : stages) {
+        ok &= addStage(s);
+      }
+      return ok;
+    }
+
+    void CoilHeatingElectricMultiStage_Impl::removeAllStages() {
+      clearExtensibleGroups();
+    }
+
+    bool CoilHeatingElectricMultiStage_Impl::removeStage(const CoilHeatingElectricMultiStageStageData& stage) {
+      boost::optional<unsigned> idx = stageIndex(stage);
+      if (!idx) {
+        LOG(Warn, "For " << briefDescription() << " cannot remove stage " << stage.briefDescription() << " since it is not part of it.");
+        return false;
+      }
+
+      return removeStage(idx.get());
+    }
+
+    bool CoilHeatingElectricMultiStage_Impl::removeStage(unsigned index) {
+      bool result = false;
+      if ((index > 0) && (index <= numberOfStages())) {
+        getObject<ModelObject>().eraseExtensibleGroup(index - 1);
+        result = true;
+      }
+      return result;
+    }
+
+  }  // namespace detail
+
+  CoilHeatingElectricMultiStage::CoilHeatingElectricMultiStage(const Model& model)
+    : StraightComponent(CoilHeatingElectricMultiStage::iddObjectType(), model) {
+    OS_ASSERT(getImpl<detail::CoilHeatingElectricMultiStage_Impl>());
+
+    auto always_on = model.alwaysOnDiscreteSchedule();
+    bool ok = setAvailabilitySchedule(always_on);
+    OS_ASSERT(ok);
+  }
+
+  IddObjectType CoilHeatingElectricMultiStage::iddObjectType() {
+    return IddObjectType(IddObjectType::OS_Coil_Heating_Electric_MultiStage);
+  }
+
+  Schedule CoilHeatingElectricMultiStage::availabilitySchedule() const {
+    return getImpl<detail::CoilHeatingElectricMultiStage_Impl>()->availabilitySchedule();
+  }
+
+  bool CoilHeatingElectricMultiStage::setAvailabilitySchedule(Schedule& schedule) {
+    return getImpl<detail::CoilHeatingElectricMultiStage_Impl>()->setAvailabilitySchedule(schedule);
+  }
+
+  unsigned CoilHeatingElectricMultiStage::numberOfStages() const {
+    return getImpl<detail::CoilHeatingElectricMultiStage_Impl>()->numberOfStages();
+  }
+
+  boost::optional<unsigned> CoilHeatingElectricMultiStage::stageIndex(const CoilHeatingElectricMultiStageStageData& stage) const {
+    return getImpl<detail::CoilHeatingElectricMultiStage_Impl>()->stageIndex(stage);
+  }
+
+  std::vector<CoilHeatingElectricMultiStageStageData> CoilHeatingElectricMultiStage::stages() const {
+    return getImpl<detail::CoilHeatingElectricMultiStage_Impl>()->stages();
+  }
+
+  bool CoilHeatingElectricMultiStage::addStage(const CoilHeatingElectricMultiStageStageData& stage) {
+    return getImpl<detail::CoilHeatingElectricMultiStage_Impl>()->addStage(stage);
+  }
+
+  bool CoilHeatingElectricMultiStage::addStage(const CoilHeatingElectricMultiStageStageData& stage, unsigned index) {
+    return getImpl<detail::CoilHeatingElectricMultiStage_Impl>()->addStage(stage, index);
+  }
+
+  bool CoilHeatingElectricMultiStage::setStageIndex(const CoilHeatingElectricMultiStageStageData& stage, unsigned index) {
+    return getImpl<detail::CoilHeatingElectricMultiStage_Impl>()->setStageIndex(stage, index);
+  }
+
+  bool CoilHeatingElectricMultiStage::setStages(const std::vector<CoilHeatingElectricMultiStageStageData>& stages) {
+    return getImpl<detail::CoilHeatingElectricMultiStage_Impl>()->setStages(stages);
+  }
+
+  void CoilHeatingElectricMultiStage::removeAllStages() {
+    getImpl<detail::CoilHeatingElectricMultiStage_Impl>()->removeAllStages();
+  }
+
+  bool CoilHeatingElectricMultiStage::removeStage(const CoilHeatingElectricMultiStageStageData& stage) {
+    return getImpl<detail::CoilHeatingElectricMultiStage_Impl>()->removeStage(stage);
+  }
+
+  bool CoilHeatingElectricMultiStage::removeStage(unsigned index) {
+    return getImpl<detail::CoilHeatingElectricMultiStage_Impl>()->removeStage(index);
+  }
+
+  /// @cond
+  CoilHeatingElectricMultiStage::CoilHeatingElectricMultiStage(std::shared_ptr<detail::CoilHeatingElectricMultiStage_Impl> impl)
+    : StraightComponent(std::move(impl)) {}
+  /// @endcond
+
+}  // namespace model
+}  // namespace openstudio
diff --git a/src/model/CoilHeatingElectricMultiStage.hpp b/src/model/CoilHeatingElectricMultiStage.hpp
new file mode 100644
index 00000000000..f8738533ddc
--- /dev/null
+++ b/src/model/CoilHeatingElectricMultiStage.hpp
@@ -0,0 +1,156 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#ifndef MODEL_COILHEATINGELECTRICMULTISTAGE_HPP
+#define MODEL_COILHEATINGELECTRICMULTISTAGE_HPP
+
+#include "ModelAPI.hpp"
+#include "StraightComponent.hpp"
+
+namespace openstudio {
+
+namespace model {
+
+  class Schedule;
+  class CoilHeatingElectricMultiStageStageData;
+
+  namespace detail {
+
+    class CoilHeatingElectricMultiStage_Impl;
+
+  }  // namespace detail
+
+  /** CoilHeatingElectricMultiStage is a StraightComponent that wraps the OpenStudio IDD object 'OS:Coil:Heating:Electric:MultiStage'.
+ * Performance data for each stage is contained in a separate ModelObject called OS:Coil:Heating:Electric:MultiStage:StageData **/
+  class MODEL_API CoilHeatingElectricMultiStage : public StraightComponent
+  {
+   public:
+    /** @name Constructors and Destructors */
+    //@{
+
+    explicit CoilHeatingElectricMultiStage(const Model& model);
+
+    virtual ~CoilHeatingElectricMultiStage() {}
+
+    //@}
+
+    static IddObjectType iddObjectType();
+
+    /** @name Getters */
+    //@{
+
+    Schedule availabilitySchedule() const;
+
+    //@}
+    /** @name Setters */
+    //@{
+
+    bool setAvailabilitySchedule(Schedule& schedule);
+
+    //@}
+    /** @name Other */
+    //@{
+
+    /** Return the performance data for each stage. **/
+    std::vector<CoilHeatingElectricMultiStageStageData> stages() const;
+
+    unsigned numberOfStages() const;
+
+    /*
+   * Get the index of a given CoilHeatingElectricMultiStageStageData (1-indexed)
+   */
+    boost::optional<unsigned> stageIndex(const CoilHeatingElectricMultiStageStageData& stage) const;
+
+    /*
+   * Add a new stage after all of the existing stages.
+   */
+    bool addStage(const CoilHeatingElectricMultiStageStageData& stage);
+
+    /*
+   * Add a new CoilHeatingElectricMultiStageStageData to the list which a given index (1 to x).
+   * Internally calls addStage then setStageIndex, see remarks there
+   */
+    bool addStage(const CoilHeatingElectricMultiStageStageData& stage, unsigned index);
+
+    /*
+   * You can shuffle the priority of a given CoilHeatingElectricMultiStageStageData after having added it
+   * If index is below 1, it's reset to 1.
+   * If index is greater than the number of stages, will reset to last
+   */
+    bool setStageIndex(const CoilHeatingElectricMultiStageStageData& stage, unsigned index);
+
+    /*
+   * Set all stages using a list of CoilHeatingElectricMultiStageStageDatas
+   * Internally calls addStage, and will return the global status, but will continue trying if there are problems
+   * (eg: if you make a vector larger than the number of accepted stages, or a vector that has a stage from another model, the valid stages will be
+   * added indeed, but it'll eventually return false)
+   */
+    bool setStages(const std::vector<CoilHeatingElectricMultiStageStageData>& stages);
+
+    /*
+   * Removes all CoilHeatingElectricMultiStageStageDatas in this object
+   */
+    void removeAllStages();
+
+    /*
+   * Remove the given CoilHeatingElectricMultiStageStageData from this object's stages
+   */
+    bool removeStage(const CoilHeatingElectricMultiStageStageData& stage);
+
+    /*
+   * Remove the CoilHeatingElectricMultiStageStageData at the given index (1-indexed)
+   */
+    bool removeStage(unsigned index);
+
+    //@}
+   protected:
+    /// @cond
+    typedef detail::CoilHeatingElectricMultiStage_Impl ImplType;
+
+    explicit CoilHeatingElectricMultiStage(std::shared_ptr<detail::CoilHeatingElectricMultiStage_Impl> impl);
+
+    friend class detail::CoilHeatingElectricMultiStage_Impl;
+    friend class Model;
+    friend class IdfObject;
+    friend class openstudio::detail::IdfObject_Impl;
+    /// @endcond
+   private:
+    REGISTER_LOGGER("openstudio.model.CoilHeatingElectricMultiStage");
+  };
+
+  /** \relates CoilHeatingElectricMultiStage*/
+  typedef boost::optional<CoilHeatingElectricMultiStage> OptionalCoilHeatingElectricMultiStage;
+
+  /** \relates CoilHeatingElectricMultiStage*/
+  typedef std::vector<CoilHeatingElectricMultiStage> CoilHeatingElectricMultiStageVector;
+
+}  // namespace model
+}  // namespace openstudio
+
+#endif  // MODEL_COILHEATINGELECTRICMULTISTAGE_HPP
diff --git a/src/model/CoilHeatingElectricMultiStageStageData.cpp b/src/model/CoilHeatingElectricMultiStageStageData.cpp
new file mode 100644
index 00000000000..228a123b2ce
--- /dev/null
+++ b/src/model/CoilHeatingElectricMultiStageStageData.cpp
@@ -0,0 +1,231 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include "CoilHeatingElectricMultiStageStageData.hpp"
+#include "CoilHeatingElectricMultiStageStageData_Impl.hpp"
+#include "../model/CoilHeatingElectricMultiStage.hpp"
+#include "../model/CoilHeatingElectricMultiStage_Impl.hpp"
+#include "../model/Model.hpp"
+#include "../model/Model_Impl.hpp"
+#include <utilities/idd/OS_Coil_Heating_Electric_MultiStage_StageData_FieldEnums.hxx>
+#include <utilities/idd/IddEnums.hxx>
+#include "../utilities/units/Unit.hpp"
+#include "../utilities/core/Assert.hpp"
+
+namespace openstudio {
+namespace model {
+
+  namespace detail {
+
+    CoilHeatingElectricMultiStageStageData_Impl::CoilHeatingElectricMultiStageStageData_Impl(const IdfObject& idfObject, Model_Impl* model,
+                                                                                             bool keepHandle)
+      : ModelObject_Impl(idfObject, model, keepHandle) {
+      OS_ASSERT(idfObject.iddObject().type() == CoilHeatingElectricMultiStageStageData::iddObjectType());
+    }
+
+    CoilHeatingElectricMultiStageStageData_Impl::CoilHeatingElectricMultiStageStageData_Impl(const openstudio::detail::WorkspaceObject_Impl& other,
+                                                                                             Model_Impl* model, bool keepHandle)
+      : ModelObject_Impl(other, model, keepHandle) {
+      OS_ASSERT(other.iddObject().type() == CoilHeatingElectricMultiStageStageData::iddObjectType());
+    }
+
+    CoilHeatingElectricMultiStageStageData_Impl::CoilHeatingElectricMultiStageStageData_Impl(const CoilHeatingElectricMultiStageStageData_Impl& other,
+                                                                                             Model_Impl* model, bool keepHandle)
+      : ModelObject_Impl(other, model, keepHandle) {}
+
+    const std::vector<std::string>& CoilHeatingElectricMultiStageStageData_Impl::outputVariableNames() const {
+      static const std::vector<std::string> result;
+      return result;
+    }
+
+    IddObjectType CoilHeatingElectricMultiStageStageData_Impl::iddObjectType() const {
+      return CoilHeatingElectricMultiStageStageData::iddObjectType();
+    }
+
+    double CoilHeatingElectricMultiStageStageData_Impl::efficiency() const {
+      boost::optional<double> value = getDouble(OS_Coil_Heating_Electric_MultiStage_StageDataFields::Efficiency, true);
+      OS_ASSERT(value);
+      return value.get();
+    }
+
+    boost::optional<double> CoilHeatingElectricMultiStageStageData_Impl::nominalCapacity() const {
+      return getDouble(OS_Coil_Heating_Electric_MultiStage_StageDataFields::NominalCapacity, true);
+    }
+
+    bool CoilHeatingElectricMultiStageStageData_Impl::isNominalCapacityAutosized() const {
+      bool result = false;
+      boost::optional<std::string> value = getString(OS_Coil_Heating_Electric_MultiStage_StageDataFields::NominalCapacity, true);
+      if (value) {
+        result = openstudio::istringEqual(value.get(), "autosize");
+      }
+      return result;
+    }
+
+    bool CoilHeatingElectricMultiStageStageData_Impl::setEfficiency(double Efficiency) {
+      bool result = setDouble(OS_Coil_Heating_Electric_MultiStage_StageDataFields::Efficiency, Efficiency);
+      return result;
+    }
+
+    bool CoilHeatingElectricMultiStageStageData_Impl::setNominalCapacity(boost::optional<double> NominalCapacity) {
+      bool result(false);
+      if (NominalCapacity) {
+        result = setDouble(OS_Coil_Heating_Electric_MultiStage_StageDataFields::NominalCapacity, NominalCapacity.get());
+      }
+      return result;
+    }
+
+    void CoilHeatingElectricMultiStageStageData_Impl::autosizeNominalCapacity() {
+      bool result = setString(OS_Coil_Heating_Electric_MultiStage_StageDataFields::NominalCapacity, "autosize");
+      OS_ASSERT(result);
+    }
+
+    std::vector<IdfObject> CoilHeatingElectricMultiStageStageData_Impl::remove() {
+      if (auto _coil = parentCoil()) {
+        _coil->removeStage(getObject<CoilHeatingElectricMultiStageStageData>());
+      }
+      return ModelObject_Impl::remove();
+    }
+
+    boost::optional<CoilHeatingElectricMultiStage> CoilHeatingElectricMultiStageStageData_Impl::parentCoil() const {
+      auto coils = getObject<ModelObject>().getModelObjectSources<CoilHeatingElectricMultiStage>(CoilHeatingElectricMultiStage::iddObjectType());
+      auto count = coils.size();
+      if (count == 1) {
+        return coils[0];
+      } else if (count > 1) {
+        LOG(Error, briefDescription() << " is referenced by more than one CoilHeatingElectricMultiStage, returning the first");
+        return coils[0];
+      }
+      return boost::none;
+    }
+
+    boost::optional<std::tuple<int, CoilHeatingElectricMultiStage>> CoilHeatingElectricMultiStageStageData_Impl::stageIndexAndParentCoil() const {
+
+      boost::optional<std::tuple<int, CoilHeatingElectricMultiStage>> result;
+
+      if (auto _coil = parentCoil()) {
+        result = std::make_tuple(_coil->stageIndex(getObject<CoilHeatingElectricMultiStageStageData>()).get(), _coil.get());
+      } else {
+        LOG(Warn, name().get() + " was not found inside a CoilHeatingElectricMultiStage in the model, cannot retrieve the autosized value.");
+      }
+
+      return result;
+    }
+
+    boost::optional<double> CoilHeatingElectricMultiStageStageData_Impl::autosizedNominalCapacity() const {
+      auto indexAndNameOpt = stageIndexAndParentCoil();
+      boost::optional<double> result;
+      if (!indexAndNameOpt) {
+        return result;
+      }
+      auto indexAndName = indexAndNameOpt.get();
+      int index = std::get<0>(indexAndName);
+      CoilHeatingElectricMultiStage parentCoil = std::get<1>(indexAndName);
+      std::string sqlField = "Design Size Stage " + std::to_string(index) + " Nominal Capacity";
+
+      return parentCoil.getAutosizedValue(sqlField, "W");
+    }
+
+    void CoilHeatingElectricMultiStageStageData_Impl::autosize() {
+      autosizeNominalCapacity();
+    }
+
+    void CoilHeatingElectricMultiStageStageData_Impl::applySizingValues() {
+      boost::optional<double> val;
+      val = autosizedNominalCapacity();
+      if (val) {
+        setNominalCapacity(val.get());
+      }
+    }
+
+  }  // namespace detail
+
+  CoilHeatingElectricMultiStageStageData::CoilHeatingElectricMultiStageStageData(const Model& model)
+    : ModelObject(CoilHeatingElectricMultiStageStageData::iddObjectType(), model) {
+    OS_ASSERT(getImpl<detail::CoilHeatingElectricMultiStageStageData_Impl>());
+
+    setEfficiency(1.0);
+    autosizeNominalCapacity();
+  }
+
+  IddObjectType CoilHeatingElectricMultiStageStageData::iddObjectType() {
+    return {IddObjectType::OS_Coil_Heating_Electric_MultiStage_StageData};
+  }
+
+  double CoilHeatingElectricMultiStageStageData::efficiency() const {
+    return getImpl<detail::CoilHeatingElectricMultiStageStageData_Impl>()->efficiency();
+  }
+
+  boost::optional<double> CoilHeatingElectricMultiStageStageData::nominalCapacity() const {
+    return getImpl<detail::CoilHeatingElectricMultiStageStageData_Impl>()->nominalCapacity();
+  }
+
+  bool CoilHeatingElectricMultiStageStageData::isNominalCapacityAutosized() const {
+    return getImpl<detail::CoilHeatingElectricMultiStageStageData_Impl>()->isNominalCapacityAutosized();
+  }
+
+  bool CoilHeatingElectricMultiStageStageData::setEfficiency(double Efficiency) {
+    return getImpl<detail::CoilHeatingElectricMultiStageStageData_Impl>()->setEfficiency(Efficiency);
+  }
+
+  bool CoilHeatingElectricMultiStageStageData::setNominalCapacity(double NominalCapacity) {
+    return getImpl<detail::CoilHeatingElectricMultiStageStageData_Impl>()->setNominalCapacity(NominalCapacity);
+  }
+
+  void CoilHeatingElectricMultiStageStageData::autosizeNominalCapacity() {
+    getImpl<detail::CoilHeatingElectricMultiStageStageData_Impl>()->autosizeNominalCapacity();
+  }
+
+  /// @cond
+  CoilHeatingElectricMultiStageStageData::CoilHeatingElectricMultiStageStageData(
+    std::shared_ptr<detail::CoilHeatingElectricMultiStageStageData_Impl> impl)
+    : ModelObject(std::move(impl)) {}
+  /// @endcond
+
+  boost::optional<double> CoilHeatingElectricMultiStageStageData::autosizedNominalCapacity() const {
+    return getImpl<detail::CoilHeatingElectricMultiStageStageData_Impl>()->autosizedNominalCapacity();
+  }
+
+  void CoilHeatingElectricMultiStageStageData::autosize() {
+    return getImpl<detail::CoilHeatingElectricMultiStageStageData_Impl>()->autosize();
+  }
+
+  void CoilHeatingElectricMultiStageStageData::applySizingValues() {
+    return getImpl<detail::CoilHeatingElectricMultiStageStageData_Impl>()->applySizingValues();
+  }
+
+  boost::optional<CoilHeatingElectricMultiStage> CoilHeatingElectricMultiStageStageData::parentCoil() const {
+    return getImpl<detail::CoilHeatingElectricMultiStageStageData_Impl>()->parentCoil();
+  }
+
+  boost::optional<std::tuple<int, CoilHeatingElectricMultiStage>> CoilHeatingElectricMultiStageStageData::stageIndexAndParentCoil() const {
+    return getImpl<detail::CoilHeatingElectricMultiStageStageData_Impl>()->stageIndexAndParentCoil();
+  }
+
+}  // namespace model
+}  // namespace openstudio
diff --git a/src/model/CoilHeatingElectricMultiStageStageData.hpp b/src/model/CoilHeatingElectricMultiStageStageData.hpp
new file mode 100644
index 00000000000..d0a67a4a649
--- /dev/null
+++ b/src/model/CoilHeatingElectricMultiStageStageData.hpp
@@ -0,0 +1,123 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#ifndef MODEL_COILHEATINGELECTRICMULTISTAGESTAGEDATA_HPP
+#define MODEL_COILHEATINGELECTRICMULTISTAGESTAGEDATA_HPP
+
+#include "ModelAPI.hpp"
+#include "ModelObject.hpp"
+#include "../model/CoilHeatingElectricMultiStage.hpp"
+
+namespace openstudio {
+
+namespace model {
+
+  namespace detail {
+
+    class CoilHeatingElectricMultiStageStageData_Impl;
+
+  }  // namespace detail
+
+  /** CoilHeatingElectricMultiStageStageData is a ModelObject that wraps the OpenStudio IDD object 'OS:Coil:Heating:Electric:MultiStage:StageData'. */
+  class MODEL_API CoilHeatingElectricMultiStageStageData : public ModelObject
+  {
+   public:
+    /** @name Constructors and Destructors */
+    //@{
+
+    explicit CoilHeatingElectricMultiStageStageData(const Model& model);
+
+    virtual ~CoilHeatingElectricMultiStageStageData() = default;
+
+    //@}
+
+    static IddObjectType iddObjectType();
+
+    /** @name Getters */
+    //@{
+
+    double efficiency() const;
+
+    boost::optional<double> nominalCapacity() const;
+
+    bool isNominalCapacityAutosized() const;
+
+    //@}
+    /** @name Setters */
+    //@{
+
+    bool setEfficiency(double StageEfficiency);
+
+    bool setNominalCapacity(double StageNominalCapacity);
+
+    void autosizeNominalCapacity();
+
+    //@}
+    /** @name Other */
+    //@{
+
+    // Returns the CoilHeatingElectricMultiStage that references it if any
+    boost::optional<CoilHeatingElectricMultiStage> parentCoil() const;
+
+    boost::optional<double> autosizedNominalCapacity() const;
+
+    void autosize();
+
+    void applySizingValues();
+
+    //@}
+   protected:
+    /// @cond
+    typedef detail::CoilHeatingElectricMultiStageStageData_Impl ImplType;
+
+    explicit CoilHeatingElectricMultiStageStageData(std::shared_ptr<detail::CoilHeatingElectricMultiStageStageData_Impl> impl);
+
+    friend class detail::CoilHeatingElectricMultiStageStageData_Impl;
+    friend class Model;
+    friend class IdfObject;
+    friend class openstudio::detail::IdfObject_Impl;
+    /// @endcond
+   private:
+    REGISTER_LOGGER("openstudio.model.CoilHeatingElectricMultiStageStageData");
+
+    // Used to determine the index of this performance data in the
+    // list of stages in the parent object.
+    boost::optional<std::tuple<int, CoilHeatingElectricMultiStage>> stageIndexAndParentCoil() const;
+  };
+
+  /** \relates CoilHeatingElectricMultiStageStageData*/
+  typedef boost::optional<CoilHeatingElectricMultiStageStageData> OptionalCoilHeatingElectricMultiStageStageData;
+
+  /** \relates CoilHeatingElectricMultiStageStageData*/
+  typedef std::vector<CoilHeatingElectricMultiStageStageData> CoilHeatingElectricMultiStageStageDataVector;
+
+}  // namespace model
+}  // namespace openstudio
+
+#endif  // MODEL_COILHEATINGELECTRICMULTISTAGESTAGEDATA_HPP
diff --git a/src/model/CoilHeatingElectricMultiStageStageData_Impl.hpp b/src/model/CoilHeatingElectricMultiStageStageData_Impl.hpp
new file mode 100644
index 00000000000..5d88c549c01
--- /dev/null
+++ b/src/model/CoilHeatingElectricMultiStageStageData_Impl.hpp
@@ -0,0 +1,117 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#ifndef MODEL_COILHEATINGELECTRICMULTISTAGESTAGEDATA_IMPL_HPP
+#define MODEL_COILHEATINGELECTRICMULTISTAGESTAGEDATA_IMPL_HPP
+
+#include "ModelAPI.hpp"
+#include "ModelObject_Impl.hpp"
+
+namespace openstudio {
+namespace model {
+
+  class CoilHeatingElectricMultiStage;
+
+  namespace detail {
+
+    /** CoilHeatingElectricMultiStageStageData_Impl is a ModelObject_Impl that is the implementation class for CoilHeatingElectricMultiStageStageData.*/
+    class MODEL_API CoilHeatingElectricMultiStageStageData_Impl : public ModelObject_Impl
+    {
+     public:
+      /** @name Constructors and Destructors */
+      //@{
+
+      CoilHeatingElectricMultiStageStageData_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle);
+
+      CoilHeatingElectricMultiStageStageData_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model, bool keepHandle);
+
+      CoilHeatingElectricMultiStageStageData_Impl(const CoilHeatingElectricMultiStageStageData_Impl& other, Model_Impl* model, bool keepHandle);
+
+      virtual ~CoilHeatingElectricMultiStageStageData_Impl() = default;
+
+      //@}
+      /** @name Virtual Methods */
+      //@{
+
+      virtual const std::vector<std::string>& outputVariableNames() const override;
+
+      virtual IddObjectType iddObjectType() const override;
+
+      // If this object is used by any CoilHeatingElectricMultiStage, remove the corresponding extensible group to avoid having 'blanks'
+      virtual std::vector<IdfObject> remove() override;
+
+      //@}
+      /** @name Getters */
+      //@{
+
+      double efficiency() const;
+
+      boost::optional<double> nominalCapacity() const;
+
+      bool isNominalCapacityAutosized() const;
+
+      //@}
+      /** @name Setters */
+      //@{
+
+      bool setEfficiency(double StageEfficiency);
+
+      bool setNominalCapacity(boost::optional<double> StageNominalCapacity);
+
+      void autosizeNominalCapacity();
+
+      //@}
+      /** @name Other */
+      //@{
+
+      boost::optional<double> autosizedNominalCapacity() const;
+
+      void autosize();
+
+      void applySizingValues();
+
+      // Returns the CoilHeatingElectricMultiStage that references it if any
+      boost::optional<CoilHeatingElectricMultiStage> parentCoil() const;
+
+      // Used to determine the index of this performance data in the
+      // list of stages in the parent object.
+      boost::optional<std::tuple<int, CoilHeatingElectricMultiStage>> stageIndexAndParentCoil() const;
+
+      //@}
+     protected:
+     private:
+      REGISTER_LOGGER("openstudio.model.CoilHeatingElectricMultiStageStageData");
+    };
+
+  }  // namespace detail
+
+}  // namespace model
+}  // namespace openstudio
+
+#endif  // MODEL_COILHEATINGELECTRICMULTISTAGESTAGEDATA_IMPL_HPP
diff --git a/src/model/CoilHeatingElectricMultiStage_Impl.hpp b/src/model/CoilHeatingElectricMultiStage_Impl.hpp
new file mode 100644
index 00000000000..0e41a333b12
--- /dev/null
+++ b/src/model/CoilHeatingElectricMultiStage_Impl.hpp
@@ -0,0 +1,122 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#ifndef MODEL_COILHEATINGELECTRICMULTISTAGE_IMPL_HPP
+#define MODEL_COILHEATINGELECTRICMULTISTAGE_IMPL_HPP
+
+#include "ModelAPI.hpp"
+#include "StraightComponent_Impl.hpp"
+
+namespace openstudio {
+namespace model {
+
+  class Schedule;
+  class CoilHeatingElectricMultiStageStageData;
+
+  namespace detail {
+
+    /** CoilHeatingElectricMultiStage_Impl is a StraightComponent_Impl that is the implementation class for CoilHeatingElectricMultiStage.*/
+    class MODEL_API CoilHeatingElectricMultiStage_Impl : public StraightComponent_Impl
+    {
+     public:
+      /** @name Constructors and Destructors */
+      //@{
+
+      CoilHeatingElectricMultiStage_Impl(const IdfObject& idfObject, Model_Impl* model, bool keepHandle);
+
+      CoilHeatingElectricMultiStage_Impl(const openstudio::detail::WorkspaceObject_Impl& other, Model_Impl* model, bool keepHandle);
+
+      CoilHeatingElectricMultiStage_Impl(const CoilHeatingElectricMultiStage_Impl& other, Model_Impl* model, bool keepHandle);
+
+      virtual ~CoilHeatingElectricMultiStage_Impl() {}
+
+      //@}
+      /** @name Virtual Methods */
+      //@{
+
+      virtual const std::vector<std::string>& outputVariableNames() const override;
+
+      virtual IddObjectType iddObjectType() const override;
+
+      virtual std::vector<ScheduleTypeKey> getScheduleTypeKeys(const Schedule& schedule) const override;
+
+      virtual unsigned inletPort() const override;
+
+      virtual unsigned outletPort() const override;
+
+      ModelObject clone(Model model) const override;
+
+      std::vector<ModelObject> children() const override;
+
+      boost::optional<HVACComponent> containingHVACComponent() const override;
+
+      bool addToNode(Node& node) override;
+
+      //@}
+      /** @name Getters */
+      //@{
+
+      Schedule availabilitySchedule() const;
+
+      //@}
+      /** @name Setters */
+      //@{
+
+      bool setAvailabilitySchedule(Schedule& schedule);
+
+      //@}
+      /** @name Other */
+      //@{
+
+      std::vector<CoilHeatingElectricMultiStageStageData> stages() const;
+      unsigned numberOfStages() const;
+      boost::optional<unsigned> stageIndex(const CoilHeatingElectricMultiStageStageData& stage) const;
+
+      bool addStage(const CoilHeatingElectricMultiStageStageData& stage);
+      bool addStage(const CoilHeatingElectricMultiStageStageData& stage, unsigned index);
+      bool setStageIndex(const CoilHeatingElectricMultiStageStageData& stage, unsigned index);
+      bool setStages(const std::vector<CoilHeatingElectricMultiStageStageData>& stages);
+      void removeAllStages();
+      bool removeStage(const CoilHeatingElectricMultiStageStageData& stage);
+      bool removeStage(unsigned index);
+
+      //@}
+     protected:
+     private:
+      REGISTER_LOGGER("openstudio.model.CoilHeatingElectricMultiStage");
+
+      boost::optional<Schedule> optionalAvailabilitySchedule() const;
+    };
+
+  }  // namespace detail
+
+}  // namespace model
+}  // namespace openstudio
+
+#endif  // MODEL_COILHEATINGELECTRICMULTISTAGE_IMPL_HPP
diff --git a/src/model/CoilHeatingGasMultiStage.cpp b/src/model/CoilHeatingGasMultiStage.cpp
index 8083f5d1f36..118262b4504 100644
--- a/src/model/CoilHeatingGasMultiStage.cpp
+++ b/src/model/CoilHeatingGasMultiStage.cpp
@@ -37,10 +37,13 @@
 #include "Curve_Impl.hpp"
 #include "CoilHeatingGasMultiStageStageData.hpp"
 #include "CoilHeatingGasMultiStageStageData_Impl.hpp"
+#include "AirLoopHVACUnitarySystem.hpp"
+#include "AirLoopHVACUnitarySystem_Impl.hpp"
 #include "AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.hpp"
 #include "AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed_Impl.hpp"
 #include "ScheduleTypeLimits.hpp"
 #include "ScheduleTypeRegistry.hpp"
+
 #include <utilities/idd/OS_Coil_Heating_Gas_MultiStage_FieldEnums.hxx>
 #include <utilities/idd/IddEnums.hxx>
 #include "../utilities/units/Unit.hpp"
@@ -67,8 +70,8 @@ namespace model {
       : StraightComponent_Impl(other, model, keepHandle) {}
 
     const std::vector<std::string>& CoilHeatingGasMultiStage_Impl::outputVariableNames() const {
-      static const std::vector<std::string> result{"Heating Coil Energy",
-                                                   "Heating Coil Rate",
+      static const std::vector<std::string> result{"Heating Coil Heating Energy",
+                                                   "Heating Coil Heating Rate",
                                                    "Heating Coil Gas Consumption",
                                                    "Heating Coil Gas Consumption Rate",
                                                    "Heating Coil Electric Consumption",
@@ -86,17 +89,95 @@ namespace model {
     std::vector<ScheduleTypeKey> CoilHeatingGasMultiStage_Impl::getScheduleTypeKeys(const Schedule& schedule) const {
       std::vector<ScheduleTypeKey> result;
       UnsignedVector fieldIndices = getSourceIndices(schedule.handle());
-      UnsignedVector::const_iterator b(fieldIndices.begin()), e(fieldIndices.end());
+      UnsignedVector::const_iterator b(fieldIndices.begin());
+      UnsignedVector::const_iterator e(fieldIndices.end());
       if (std::find(b, e, OS_Coil_Heating_Gas_MultiStageFields::AvailabilitySchedule) != e) {
-        result.push_back(ScheduleTypeKey("CoilHeatingGasMultiStage", "Availability Schedule"));
+        result.emplace_back("CoilHeatingGasMultiStage", "Availability Schedule");
+      }
+      return result;
+    }
+
+    unsigned CoilHeatingGasMultiStage_Impl::inletPort() const {
+      return OS_Coil_Heating_Gas_MultiStageFields::AirInletNode;
+    }
+
+    unsigned CoilHeatingGasMultiStage_Impl::outletPort() const {
+      return OS_Coil_Heating_Gas_MultiStageFields::AirOutletNode;
+    }
+
+    ModelObject CoilHeatingGasMultiStage_Impl::clone(Model model) const {
+      auto t_clone = StraightComponent_Impl::clone(model).cast<CoilHeatingGasMultiStage>();
+
+      // Deal with the stages: we want them cloned, so first clear and then clone + assign each
+      t_clone.removeAllStages();
+
+      for (const auto& stage : stages()) {
+        auto stageClone = stage.clone(model).cast<CoilHeatingGasMultiStageStageData>();
+        t_clone.addStage(stageClone);
+      }
+      return t_clone;
+    }
+
+    std::vector<ModelObject> CoilHeatingGasMultiStage_Impl::children() const {
+      std::vector<ModelObject> result = subsetCastVector<ModelObject>(stages());
+
+      if (auto const curve = partLoadFractionCorrelationCurve()) {
+        result.push_back(curve.get());
       }
+
       return result;
     }
 
-    boost::optional<Schedule> CoilHeatingGasMultiStage_Impl::availabilitySchedule() const {
+    boost::optional<HVACComponent> CoilHeatingGasMultiStage_Impl::containingHVACComponent() const {
+      // AirLoopHVACUnitarySystem
+      std::vector<AirLoopHVACUnitarySystem> airLoopHVACUnitarySystems = this->model().getConcreteModelObjects<AirLoopHVACUnitarySystem>();
+
+      for (const auto& airLoopHVACUnitarySystem : airLoopHVACUnitarySystems) {
+        if (boost::optional<HVACComponent> heatingCoil = airLoopHVACUnitarySystem.heatingCoil()) {
+          if (heatingCoil->handle() == this->handle()) {
+            return airLoopHVACUnitarySystem;
+          }
+        }
+      }
+
+      // AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed
+      std::vector<AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed> airLoopHVACUnitaryHeatPumpAirToAirMultiSpeeds =
+        this->model().getConcreteModelObjects<AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed>();
+
+      for (const auto& airLoopHVACUnitaryHeatPumpAirToAirMultiSpeed : airLoopHVACUnitaryHeatPumpAirToAirMultiSpeeds) {
+        if (boost::optional<HVACComponent> heatingCoil = airLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.heatingCoil()) {
+          if (heatingCoil->handle() == this->handle()) {
+            return airLoopHVACUnitaryHeatPumpAirToAirMultiSpeed;
+          }
+        }
+      }
+
+      return boost::none;
+    }
+
+    bool CoilHeatingGasMultiStage_Impl::addToNode(Node& node) {
+      return false;
+    }
+
+    boost::optional<Schedule> CoilHeatingGasMultiStage_Impl::optionalAvailabilitySchedule() const {
       return getObject<ModelObject>().getModelObjectTarget<Schedule>(OS_Coil_Heating_Gas_MultiStageFields::AvailabilitySchedule);
     }
 
+    Schedule CoilHeatingGasMultiStage_Impl::availabilitySchedule() const {
+      boost::optional<Schedule> value = optionalAvailabilitySchedule();
+      if (!value) {
+        // it is an error if we get here, however we don't want to crash
+        // so we hook up to global always on schedule
+        LOG(Error, "Required availability schedule not set, using 'Always On' schedule");
+        value = this->model().alwaysOnDiscreteSchedule();
+        OS_ASSERT(value);
+        const_cast<CoilHeatingGasMultiStage_Impl*>(this)->setAvailabilitySchedule(*value);
+        value = optionalAvailabilitySchedule();
+      }
+      OS_ASSERT(value);
+      return value.get();
+    }
+
     boost::optional<Curve> CoilHeatingGasMultiStage_Impl::partLoadFractionCorrelationCurve() const {
       return getObject<ModelObject>().getModelObjectTarget<Curve>(OS_Coil_Heating_Gas_MultiStageFields::PartLoadFractionCorrelationCurve);
     }
@@ -111,11 +192,6 @@ namespace model {
       return result;
     }
 
-    void CoilHeatingGasMultiStage_Impl::resetAvailabilitySchedule() {
-      bool result = setString(OS_Coil_Heating_Gas_MultiStageFields::AvailabilitySchedule, "");
-      OS_ASSERT(result);
-    }
-
     bool CoilHeatingGasMultiStage_Impl::setPartLoadFractionCorrelationCurve(const boost::optional<Curve>& curve) {
       bool result(false);
       if (curve) {
@@ -149,79 +225,147 @@ namespace model {
       OS_ASSERT(result);
     }
 
-    unsigned CoilHeatingGasMultiStage_Impl::inletPort() const {
-      return OS_Coil_Heating_Gas_MultiStageFields::AirInletNode;
+    unsigned CoilHeatingGasMultiStage_Impl::numberOfStages() const {
+      return numExtensibleGroups();
     }
 
-    unsigned CoilHeatingGasMultiStage_Impl::outletPort() const {
-      return OS_Coil_Heating_Gas_MultiStageFields::AirOutletNode;
+    std::vector<CoilHeatingGasMultiStageStageData> CoilHeatingGasMultiStage_Impl::stages() const {
+      std::vector<CoilHeatingGasMultiStageStageData> result;
+      for (const auto& group : extensibleGroups()) {
+        if (auto target = group.cast<WorkspaceExtensibleGroup>().getTarget(OS_Coil_Heating_Gas_MultiStageExtensibleFields::Stage)) {
+          if (auto stage = target->optionalCast<CoilHeatingGasMultiStageStageData>()) {
+            result.push_back(stage.get());
+          }
+        }
+      }
+      return result;
     }
 
-    ModelObject CoilHeatingGasMultiStage_Impl::clone(Model model) const {
-      auto t_clone = StraightComponent_Impl::clone(model).cast<CoilHeatingGasMultiStage>();
+    boost::optional<unsigned> CoilHeatingGasMultiStage_Impl::stageIndex(const CoilHeatingGasMultiStageStageData& stage) const {
 
-      auto t_stages = stages();
-      for (auto stage : t_stages) {
-        auto stageClone = stage.clone(model).cast<CoilHeatingGasMultiStageStageData>();
-        t_clone.addStage(stageClone);
+      boost::optional<unsigned> result;
+
+      auto egs = castVector<WorkspaceExtensibleGroup>(extensibleGroups());
+      auto h = openstudio::toString(stage.handle());
+      auto it = std::find_if(egs.begin(), egs.end(), [&](const WorkspaceExtensibleGroup& eg) {
+        return (eg.getField(OS_Coil_Heating_Gas_MultiStageExtensibleFields::Stage).get() == h);
+      });
+
+      // If found, we compute the index by using std::distance between the start of vector and the iterator returned by std::find_if
+      if (it != egs.end()) {
+        result = std::distance(egs.begin(), it) + 1;
       }
-      return t_clone;
-    }
 
-    std::vector<ModelObject> CoilHeatingGasMultiStage_Impl::children() const {
-      return subsetCastVector<ModelObject>(stages());
+      return result;
     }
 
-    std::vector<CoilHeatingGasMultiStageStageData> CoilHeatingGasMultiStage_Impl::stages() const {
-      std::vector<CoilHeatingGasMultiStageStageData> result;
-      auto groups = extensibleGroups();
-      for (auto group : groups) {
-        auto target = group.cast<WorkspaceExtensibleGroup>().getTarget(OS_Coil_Heating_Gas_MultiStageExtensibleFields::Stage);
-        if (target) {
-          if (auto stage = target->optionalCast<CoilHeatingGasMultiStageStageData>()) {
-            result.push_back(stage.get());
-          }
+    bool CoilHeatingGasMultiStage_Impl::addStage(const CoilHeatingGasMultiStageStageData& stage) {
+      if (auto _c = stage.parentCoil()) {
+        if (this->handle() == _c->handle()) {
+          return true;  // already the case
+        } else {
+          LOG(Error, "For " << briefDescription() << " cannot add " << stage.briefDescription()
+                            << " since this Stage is already in use by another coil ('" << _c->nameString() << "').");
+          return false;
         }
       }
+      auto group = getObject<ModelObject>().pushExtensibleGroup().cast<WorkspaceExtensibleGroup>();
+      if (group.empty()) {
+        LOG(Error, "You have reached the maximum number of stages (=" << numberOfStages() << "), occurred for " << briefDescription() << ".");
+        return false;
+      }
+      bool result = group.setPointer(OS_Coil_Heating_Gas_MultiStageExtensibleFields::Stage, stage.handle());
+      if (!result) {
+        // Something went wrong, so erase the new extensible group
+        getObject<ModelObject>().eraseExtensibleGroup(group.groupIndex());
+      }
       return result;
     }
 
-    void CoilHeatingGasMultiStage_Impl::addStage(CoilHeatingGasMultiStageStageData& stage) {
-      auto group = getObject<ModelObject>().pushExtensibleGroup().cast<WorkspaceExtensibleGroup>();
-      group.setPointer(OS_Coil_Heating_Gas_MultiStageExtensibleFields::Stage, stage.handle());
+    bool CoilHeatingGasMultiStage_Impl::setStageIndex(const CoilHeatingGasMultiStageStageData& stage, unsigned index) {
+      boost::optional<unsigned> idx = stageIndex(stage);
+      if (!idx) {
+        LOG(Warn, "For " << briefDescription() << " cannot set the index of stage " << stage.briefDescription() << " since it is not part of it.");
+        return false;
+      }
+
+      // TODO: we could just set via string instead of doing a ton of typechecking below...
+
+      std::vector<CoilHeatingGasMultiStageStageData> stageVector = stages();
+
+      if (index > stageVector.size()) {
+        LOG(Warn, "Requested a stage index of " << index << " to be assigned to " << stage.briefDescription() << ", but " << briefDescription()
+                                                << " only has " << stageVector.size() << " stages, resetting to that.");
+        index = stageVector.size();
+      } else if (index < 1) {
+        LOG(Warn, "Requested a stage index of " << index << " < 1 to be assigned to " << stage.briefDescription() << ", resetting to 1");
+        index = 1;
+      }
+
+      stageVector.erase(stageVector.begin() + idx.get() - 1);  // stageIndex is 1-indexed, and vector is 0-indexed
+
+      stageVector.insert(stageVector.begin() + (index - 1), stage);
+
+      return setStages(stageVector);
     }
 
-    boost::optional<HVACComponent> CoilHeatingGasMultiStage_Impl::containingHVACComponent() const {
-      // AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed
-      {
-        auto systems = this->model().getConcreteModelObjects<AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed>();
+    bool CoilHeatingGasMultiStage_Impl::addStage(const CoilHeatingGasMultiStageStageData& stage, unsigned index) {
+      bool ok = addStage(stage);
+      if (!ok) {
+        return false;
+      }
+      ok = setStageIndex(stage, index);
+      return ok;
+    }
 
-        for (const auto& system : systems) {
-          auto coolingCoil = system.coolingCoil();
-          if (coolingCoil.handle() == this->handle()) {
-            return system;
-          }
-        }
+    bool CoilHeatingGasMultiStage_Impl::setStages(const std::vector<CoilHeatingGasMultiStageStageData>& stages) {
+      // Clear the extensible groups, and redo them
+      bool ok = true;
+      clearExtensibleGroups();
+      for (const CoilHeatingGasMultiStageStageData& s : stages) {
+        ok &= addStage(s);
       }
+      return ok;
+    }
 
-      return boost::none;
+    void CoilHeatingGasMultiStage_Impl::removeAllStages() {
+      clearExtensibleGroups();
     }
 
-    bool CoilHeatingGasMultiStage_Impl::addToNode(Node& node) {
-      return false;
+    bool CoilHeatingGasMultiStage_Impl::removeStage(const CoilHeatingGasMultiStageStageData& stage) {
+      boost::optional<unsigned> idx = stageIndex(stage);
+      if (!idx) {
+        LOG(Warn, "For " << briefDescription() << " cannot remove stage " << stage.briefDescription() << " since it is not part of it.");
+        return false;
+      }
+
+      return removeStage(idx.get());
+    }
+
+    bool CoilHeatingGasMultiStage_Impl::removeStage(unsigned index) {
+      bool result = false;
+      if ((index > 0) && (index <= numberOfStages())) {
+        getObject<ModelObject>().eraseExtensibleGroup(index - 1);
+        result = true;
+      }
+      return result;
     }
 
   }  // namespace detail
 
   CoilHeatingGasMultiStage::CoilHeatingGasMultiStage(const Model& model) : StraightComponent(CoilHeatingGasMultiStage::iddObjectType(), model) {
     OS_ASSERT(getImpl<detail::CoilHeatingGasMultiStage_Impl>());
+
+    auto always_on = model.alwaysOnDiscreteSchedule();
+    bool ok = setAvailabilitySchedule(always_on);
+    OS_ASSERT(ok);
   }
 
   IddObjectType CoilHeatingGasMultiStage::iddObjectType() {
-    return IddObjectType(IddObjectType::OS_Coil_Heating_Gas_MultiStage);
+    return {IddObjectType::OS_Coil_Heating_Gas_MultiStage};
   }
 
-  boost::optional<Schedule> CoilHeatingGasMultiStage::availabilitySchedule() const {
+  Schedule CoilHeatingGasMultiStage::availabilitySchedule() const {
     return getImpl<detail::CoilHeatingGasMultiStage_Impl>()->availabilitySchedule();
   }
 
@@ -237,10 +381,6 @@ namespace model {
     return getImpl<detail::CoilHeatingGasMultiStage_Impl>()->setAvailabilitySchedule(schedule);
   }
 
-  void CoilHeatingGasMultiStage::resetAvailabilitySchedule() {
-    getImpl<detail::CoilHeatingGasMultiStage_Impl>()->resetAvailabilitySchedule();
-  }
-
   bool CoilHeatingGasMultiStage::setPartLoadFractionCorrelationCurve(const Curve& curve) {
     return getImpl<detail::CoilHeatingGasMultiStage_Impl>()->setPartLoadFractionCorrelationCurve(curve);
   }
@@ -257,12 +397,44 @@ namespace model {
     getImpl<detail::CoilHeatingGasMultiStage_Impl>()->resetParasiticGasLoad();
   }
 
+  unsigned CoilHeatingGasMultiStage::numberOfStages() const {
+    return getImpl<detail::CoilHeatingGasMultiStage_Impl>()->numberOfStages();
+  }
+
+  boost::optional<unsigned> CoilHeatingGasMultiStage::stageIndex(const CoilHeatingGasMultiStageStageData& stage) const {
+    return getImpl<detail::CoilHeatingGasMultiStage_Impl>()->stageIndex(stage);
+  }
+
   std::vector<CoilHeatingGasMultiStageStageData> CoilHeatingGasMultiStage::stages() const {
     return getImpl<detail::CoilHeatingGasMultiStage_Impl>()->stages();
   }
 
-  void CoilHeatingGasMultiStage::addStage(CoilHeatingGasMultiStageStageData& stage) {
-    getImpl<detail::CoilHeatingGasMultiStage_Impl>()->addStage(stage);
+  bool CoilHeatingGasMultiStage::addStage(const CoilHeatingGasMultiStageStageData& stage) {
+    return getImpl<detail::CoilHeatingGasMultiStage_Impl>()->addStage(stage);
+  }
+
+  bool CoilHeatingGasMultiStage::addStage(const CoilHeatingGasMultiStageStageData& stage, unsigned index) {
+    return getImpl<detail::CoilHeatingGasMultiStage_Impl>()->addStage(stage, index);
+  }
+
+  bool CoilHeatingGasMultiStage::setStageIndex(const CoilHeatingGasMultiStageStageData& stage, unsigned index) {
+    return getImpl<detail::CoilHeatingGasMultiStage_Impl>()->setStageIndex(stage, index);
+  }
+
+  bool CoilHeatingGasMultiStage::setStages(const std::vector<CoilHeatingGasMultiStageStageData>& stages) {
+    return getImpl<detail::CoilHeatingGasMultiStage_Impl>()->setStages(stages);
+  }
+
+  void CoilHeatingGasMultiStage::removeAllStages() {
+    getImpl<detail::CoilHeatingGasMultiStage_Impl>()->removeAllStages();
+  }
+
+  bool CoilHeatingGasMultiStage::removeStage(const CoilHeatingGasMultiStageStageData& stage) {
+    return getImpl<detail::CoilHeatingGasMultiStage_Impl>()->removeStage(stage);
+  }
+
+  bool CoilHeatingGasMultiStage::removeStage(unsigned index) {
+    return getImpl<detail::CoilHeatingGasMultiStage_Impl>()->removeStage(index);
   }
 
   /// @cond
diff --git a/src/model/CoilHeatingGasMultiStage.hpp b/src/model/CoilHeatingGasMultiStage.hpp
index 5dfb4d39d54..ac7b0866b9f 100644
--- a/src/model/CoilHeatingGasMultiStage.hpp
+++ b/src/model/CoilHeatingGasMultiStage.hpp
@@ -66,7 +66,7 @@ namespace model {
     /** @name Getters */
     //@{
 
-    boost::optional<Schedule> availabilitySchedule() const;
+    Schedule availabilitySchedule() const;
 
     boost::optional<Curve> partLoadFractionCorrelationCurve() const;
 
@@ -78,8 +78,6 @@ namespace model {
 
     bool setAvailabilitySchedule(Schedule& schedule);
 
-    void resetAvailabilitySchedule();
-
     bool setPartLoadFractionCorrelationCurve(const Curve& curve);
 
     void resetPartLoadFractionCorrelationCurve();
@@ -92,9 +90,56 @@ namespace model {
     /** @name Other */
     //@{
 
+    /** Return the performance data for each stage. **/
     std::vector<CoilHeatingGasMultiStageStageData> stages() const;
 
-    void addStage(CoilHeatingGasMultiStageStageData& stage);
+    unsigned numberOfStages() const;
+
+    /*
+   * Get the index of a given CoilHeatingGasMultiStageStageData (1-indexed)
+   */
+    boost::optional<unsigned> stageIndex(const CoilHeatingGasMultiStageStageData& stage) const;
+
+    /*
+   * Add a new stage after all of the existing stages.
+   */
+    bool addStage(const CoilHeatingGasMultiStageStageData& stage);
+
+    /*
+   * Add a new CoilHeatingGasMultiStageStageData to the list which a given index (1 to x).
+   * Internally calls addStage then setStageIndex, see remarks there
+   */
+    bool addStage(const CoilHeatingGasMultiStageStageData& stage, unsigned index);
+
+    /*
+   * You can shuffle the priority of a given CoilHeatingGasMultiStageStageData after having added it
+   * If index is below 1, it's reset to 1.
+   * If index is greater than the number of stages, will reset to last
+   */
+    bool setStageIndex(const CoilHeatingGasMultiStageStageData& stage, unsigned index);
+
+    /*
+   * Set all stages using a list of CoilHeatingGasMultiStageStageDatas
+   * Internally calls addStage, and will return the global status, but will continue trying if there are problems
+   * (eg: if you make a vector larger than the number of accepted stages, or a vector that has a stage from another model, the valid stages will be
+   * added indeed, but it'll eventually return false)
+   */
+    bool setStages(const std::vector<CoilHeatingGasMultiStageStageData>& stages);
+
+    /*
+   * Removes all CoilHeatingGasMultiStageStageDatas in this object
+   */
+    void removeAllStages();
+
+    /*
+   * Remove the given CoilHeatingGasMultiStageStageData from this object's stages
+   */
+    bool removeStage(const CoilHeatingGasMultiStageStageData& stage);
+
+    /*
+   * Remove the CoilHeatingGasMultiStageStageData at the given index (1-indexed)
+   */
+    bool removeStage(unsigned index);
 
     //@}
    protected:
diff --git a/src/model/CoilHeatingGasMultiStageStageData.cpp b/src/model/CoilHeatingGasMultiStageStageData.cpp
index 4b8d15df91b..70abda46013 100644
--- a/src/model/CoilHeatingGasMultiStageStageData.cpp
+++ b/src/model/CoilHeatingGasMultiStageStageData.cpp
@@ -116,36 +116,36 @@ namespace model {
       return result;
     }
 
+    std::vector<IdfObject> CoilHeatingGasMultiStageStageData_Impl::remove() {
+      if (auto _coil = parentCoil()) {
+        _coil->removeStage(getObject<CoilHeatingGasMultiStageStageData>());
+      }
+      return ModelObject_Impl::remove();
+    }
+
+    boost::optional<CoilHeatingGasMultiStage> CoilHeatingGasMultiStageStageData_Impl::parentCoil() const {
+      auto coils = getObject<ModelObject>().getModelObjectSources<CoilHeatingGasMultiStage>(CoilHeatingGasMultiStage::iddObjectType());
+      auto count = coils.size();
+      if (count == 1) {
+        return coils[0];
+      } else if (count > 1) {
+        LOG(Error, briefDescription() << " is referenced by more than one CoilHeatingGasMultiStage, returning the first");
+        return coils[0];
+      }
+      return boost::none;
+    }
+
     boost::optional<std::tuple<int, CoilHeatingGasMultiStage>> CoilHeatingGasMultiStageStageData_Impl::stageIndexAndParentCoil() const {
 
       boost::optional<std::tuple<int, CoilHeatingGasMultiStage>> result;
 
-      // This coil performance object can only be found in a CoilHeatingGasMultiStage
-      // Check all CoilHeatingGasMultiStages in the model, seeing if this is inside of one of them.
-      boost::optional<int> stageIndex;
-      boost::optional<CoilHeatingGasMultiStage> parentCoil;
-      auto coilHeatingGasMultiStages = this->model().getConcreteModelObjects<CoilHeatingGasMultiStage>();
-      for (const auto& coilInModel : coilHeatingGasMultiStages) {
-        // Check the coil performance objects in this coil to see if one of them is this object
-        std::vector<CoilHeatingGasMultiStageStageData> perfStages = coilInModel.stages();
-        int i = 1;
-        for (auto perfStage : perfStages) {
-          if (perfStage.handle() == this->handle()) {
-            stageIndex = i;
-            parentCoil = coilInModel;
-            break;
-          }
-          i++;
-        }
+      if (auto _coil = parentCoil()) {
+        result = std::make_tuple(_coil->stageIndex(getObject<CoilHeatingGasMultiStageStageData>()).get(), _coil.get());
+      } else {
+        LOG(Warn, name().get() + " was not found inside a CoilHeatingGasMultiStage in the model, cannot retrieve the autosized value.");
       }
 
-      // Warn if this coil performance object was not found inside a coil
-      if (!parentCoil) {
-        LOG(Warn, name().get() + " was not found inside a CoilCoolingDXMultiSpeed in the model, cannot retrieve the autosized value.");
-        return result;
-      }
-
-      return std::make_tuple(stageIndex.get(), parentCoil.get());
+      return result;
     }
 
     boost::optional<double> CoilHeatingGasMultiStageStageData_Impl::autosizedNominalCapacity() const {
@@ -186,7 +186,7 @@ namespace model {
   }
 
   IddObjectType CoilHeatingGasMultiStageStageData::iddObjectType() {
-    return IddObjectType(IddObjectType::OS_Coil_Heating_Gas_MultiStage_StageData);
+    return {IddObjectType::OS_Coil_Heating_Gas_MultiStage_StageData};
   }
 
   double CoilHeatingGasMultiStageStageData::gasBurnerEfficiency() const {
@@ -238,6 +238,10 @@ namespace model {
     return getImpl<detail::CoilHeatingGasMultiStageStageData_Impl>()->applySizingValues();
   }
 
+  boost::optional<CoilHeatingGasMultiStage> CoilHeatingGasMultiStageStageData::parentCoil() const {
+    return getImpl<detail::CoilHeatingGasMultiStageStageData_Impl>()->parentCoil();
+  }
+
   boost::optional<std::tuple<int, CoilHeatingGasMultiStage>> CoilHeatingGasMultiStageStageData::stageIndexAndParentCoil() const {
     return getImpl<detail::CoilHeatingGasMultiStageStageData_Impl>()->stageIndexAndParentCoil();
   }
diff --git a/src/model/CoilHeatingGasMultiStageStageData.hpp b/src/model/CoilHeatingGasMultiStageStageData.hpp
index 96d5eaab95b..b1686d9f4e6 100644
--- a/src/model/CoilHeatingGasMultiStageStageData.hpp
+++ b/src/model/CoilHeatingGasMultiStageStageData.hpp
@@ -53,7 +53,7 @@ namespace model {
 
     explicit CoilHeatingGasMultiStageStageData(const Model& model);
 
-    virtual ~CoilHeatingGasMultiStageStageData() {}
+    virtual ~CoilHeatingGasMultiStageStageData() = default;
 
     //@}
 
@@ -86,6 +86,9 @@ namespace model {
     /** @name Other */
     //@{
 
+    // Returns the CoilHeatingGasMultiStage that references it if any
+    boost::optional<CoilHeatingGasMultiStage> parentCoil() const;
+
     boost::optional<double> autosizedNominalCapacity() const;
 
     void autosize();
diff --git a/src/model/CoilHeatingGasMultiStageStageData_Impl.hpp b/src/model/CoilHeatingGasMultiStageStageData_Impl.hpp
index bd823661d8e..54eb7f892b7 100644
--- a/src/model/CoilHeatingGasMultiStageStageData_Impl.hpp
+++ b/src/model/CoilHeatingGasMultiStageStageData_Impl.hpp
@@ -32,11 +32,12 @@
 
 #include "ModelAPI.hpp"
 #include "ModelObject_Impl.hpp"
-#include "../model/CoilHeatingGasMultiStage.hpp"
 
 namespace openstudio {
 namespace model {
 
+  class CoilHeatingGasMultiStage;
+
   namespace detail {
 
     /** CoilHeatingGasMultiStageStageData_Impl is a ModelObject_Impl that is the implementation class for CoilHeatingGasMultiStageStageData.*/
@@ -52,7 +53,7 @@ namespace model {
 
       CoilHeatingGasMultiStageStageData_Impl(const CoilHeatingGasMultiStageStageData_Impl& other, Model_Impl* model, bool keepHandle);
 
-      virtual ~CoilHeatingGasMultiStageStageData_Impl() {}
+      virtual ~CoilHeatingGasMultiStageStageData_Impl() = default;
 
       //@}
       /** @name Virtual Methods */
@@ -62,6 +63,9 @@ namespace model {
 
       virtual IddObjectType iddObjectType() const override;
 
+      // If this object is used by any CoilHeatingGasMultiStage, remove the corresponding extensible group to avoid having 'blanks'
+      virtual std::vector<IdfObject> remove() override;
+
       //@}
       /** @name Getters */
       //@{
@@ -76,10 +80,6 @@ namespace model {
 
       boost::optional<double> autosizedNominalCapacity() const;
 
-      void autosize();
-
-      void applySizingValues();
-
       //@}
       /** @name Setters */
       //@{
@@ -96,6 +96,13 @@ namespace model {
       /** @name Other */
       //@{
 
+      void autosize();
+
+      void applySizingValues();
+
+      // Returns the CoilHeatingGasMultiStage that references it if any
+      boost::optional<CoilHeatingGasMultiStage> parentCoil() const;
+
       // Used to determine the index of this performance data in the
       // list of stages in the parent object.
       boost::optional<std::tuple<int, CoilHeatingGasMultiStage>> stageIndexAndParentCoil() const;
diff --git a/src/model/CoilHeatingGasMultiStage_Impl.hpp b/src/model/CoilHeatingGasMultiStage_Impl.hpp
index 27cf9779b6f..09fa36576fe 100644
--- a/src/model/CoilHeatingGasMultiStage_Impl.hpp
+++ b/src/model/CoilHeatingGasMultiStage_Impl.hpp
@@ -67,11 +67,23 @@ namespace model {
 
       virtual std::vector<ScheduleTypeKey> getScheduleTypeKeys(const Schedule& schedule) const override;
 
+      virtual unsigned inletPort() const override;
+
+      virtual unsigned outletPort() const override;
+
+      ModelObject clone(Model model) const override;
+
+      std::vector<ModelObject> children() const override;
+
+      boost::optional<HVACComponent> containingHVACComponent() const override;
+
+      bool addToNode(Node& node) override;
+
       //@}
       /** @name Getters */
       //@{
 
-      boost::optional<Schedule> availabilitySchedule() const;
+      Schedule availabilitySchedule() const;
 
       boost::optional<Curve> partLoadFractionCorrelationCurve() const;
 
@@ -83,8 +95,6 @@ namespace model {
 
       bool setAvailabilitySchedule(Schedule& schedule);
 
-      void resetAvailabilitySchedule();
-
       bool setPartLoadFractionCorrelationCurve(const boost::optional<Curve>& curve);
 
       void resetPartLoadFractionCorrelationCurve();
@@ -97,26 +107,24 @@ namespace model {
       /** @name Other */
       //@{
 
-      virtual unsigned inletPort() const override;
-
-      virtual unsigned outletPort() const override;
-
-      ModelObject clone(Model model) const override;
-
-      std::vector<ModelObject> children() const override;
-
       std::vector<CoilHeatingGasMultiStageStageData> stages() const;
+      unsigned numberOfStages() const;
+      boost::optional<unsigned> stageIndex(const CoilHeatingGasMultiStageStageData& stage) const;
 
-      void addStage(CoilHeatingGasMultiStageStageData& stage);
-
-      boost::optional<HVACComponent> containingHVACComponent() const override;
-
-      bool addToNode(Node& node) override;
+      bool addStage(const CoilHeatingGasMultiStageStageData& stage);
+      bool addStage(const CoilHeatingGasMultiStageStageData& stage, unsigned index);
+      bool setStageIndex(const CoilHeatingGasMultiStageStageData& stage, unsigned index);
+      bool setStages(const std::vector<CoilHeatingGasMultiStageStageData>& stages);
+      void removeAllStages();
+      bool removeStage(const CoilHeatingGasMultiStageStageData& stage);
+      bool removeStage(unsigned index);
 
       //@}
      protected:
      private:
       REGISTER_LOGGER("openstudio.model.CoilHeatingGasMultiStage");
+
+      boost::optional<Schedule> optionalAvailabilitySchedule() const;
     };
 
   }  // namespace detail
diff --git a/src/model/ConcreteModelObjects.hpp b/src/model/ConcreteModelObjects.hpp
index b3e02643484..da0cc091e1e 100644
--- a/src/model/ConcreteModelObjects.hpp
+++ b/src/model/ConcreteModelObjects.hpp
@@ -136,6 +136,8 @@
 #include "CoilHeatingDXSingleSpeed.hpp"
 #include "CoilHeatingDXVariableRefrigerantFlow.hpp"
 #include "CoilHeatingElectric.hpp"
+#include "CoilHeatingElectricMultiStage.hpp"
+#include "CoilHeatingElectricMultiStageStageData.hpp"
 #include "CoilHeatingFourPipeBeam.hpp"
 #include "CoilHeatingGas.hpp"
 #include "CoilHeatingGasMultiStage.hpp"
@@ -670,6 +672,8 @@
 #include "CoilHeatingDXSingleSpeed_Impl.hpp"
 #include "CoilHeatingDXVariableRefrigerantFlow_Impl.hpp"
 #include "CoilHeatingElectric_Impl.hpp"
+#include "CoilHeatingElectricMultiStage_Impl.hpp"
+#include "CoilHeatingElectricMultiStageStageData_Impl.hpp"
 #include "CoilHeatingFourPipeBeam_Impl.hpp"
 #include "CoilHeatingGas_Impl.hpp"
 #include "CoilHeatingGasMultiStage_Impl.hpp"
diff --git a/src/model/Model.cpp b/src/model/Model.cpp
index db96379c9c5..7511c1310f1 100644
--- a/src/model/Model.cpp
+++ b/src/model/Model.cpp
@@ -1749,6 +1749,8 @@ namespace model {
           modelObj->autosize();
         } else if (auto modelObj = optModelObj.optionalCast<CoilHeatingGasMultiStageStageData>()) {  // CoilHeatingGasMultiStageStageData
           modelObj->autosize();
+        } else if (auto modelObj = optModelObj.optionalCast<CoilHeatingElectricMultiStageStageData>()) {  // CoilHeatingElectricMultiStageStageData
+          modelObj->autosize();
         } else if (auto modelObj =
                      optModelObj.optionalCast<
                        HeatExchangerDesiccantBalancedFlowPerformanceDataType1>()) {  // HeatExchangerDesiccantBalancedFlowPerformanceDataType1
@@ -1784,6 +1786,8 @@ namespace model {
           modelObj->applySizingValues();
         } else if (auto modelObj = optModelObj.optionalCast<CoilHeatingGasMultiStageStageData>()) {  // CoilHeatingGasMultiStageStageData
           modelObj->applySizingValues();
+        } else if (auto modelObj = optModelObj.optionalCast<CoilHeatingElectricMultiStageStageData>()) {  // CoilHeatingElectricMultiStageStageData
+          modelObj->applySizingValues();
         } else if (auto modelObj =
                      optModelObj.optionalCast<
                        HeatExchangerDesiccantBalancedFlowPerformanceDataType1>()) {  // HeatExchangerDesiccantBalancedFlowPerformanceDataType1
@@ -3904,6 +3908,8 @@ namespace model {
     REGISTER_CONSTRUCTOR(CoilHeatingDXSingleSpeed);
     REGISTER_CONSTRUCTOR(CoilHeatingDXVariableRefrigerantFlow);
     REGISTER_CONSTRUCTOR(CoilHeatingElectric);
+    REGISTER_CONSTRUCTOR(CoilHeatingElectricMultiStage);
+    REGISTER_CONSTRUCTOR(CoilHeatingElectricMultiStageStageData);
     REGISTER_CONSTRUCTOR(CoilHeatingFourPipeBeam);
     REGISTER_CONSTRUCTOR(CoilHeatingGas);
     REGISTER_CONSTRUCTOR(CoilHeatingGasMultiStage);
@@ -4458,6 +4464,8 @@ namespace model {
     REGISTER_COPYCONSTRUCTORS(CoilHeatingDXSingleSpeed);
     REGISTER_COPYCONSTRUCTORS(CoilHeatingDXVariableRefrigerantFlow);
     REGISTER_COPYCONSTRUCTORS(CoilHeatingElectric);
+    REGISTER_COPYCONSTRUCTORS(CoilHeatingElectricMultiStage);
+    REGISTER_COPYCONSTRUCTORS(CoilHeatingElectricMultiStageStageData);
     REGISTER_COPYCONSTRUCTORS(CoilHeatingFourPipeBeam);
     REGISTER_COPYCONSTRUCTORS(CoilHeatingGas);
     REGISTER_COPYCONSTRUCTORS(CoilHeatingGasMultiStage);
diff --git a/src/model/ModelHVAC.i b/src/model/ModelHVAC.i
index 91590c33ddd..4265d307735 100644
--- a/src/model/ModelHVAC.i
+++ b/src/model/ModelHVAC.i
@@ -60,6 +60,8 @@
   // Defined in ModelStraightComponent.i
   %ignore openstudio::model::CoilCoolingDXMultiSpeedStageData::parentCoil;
   %ignore openstudio::model::CoilHeatingDXMultiSpeedStageData::parentCoil;
+  %ignore openstudio::model::CoilHeatingGasMultiStageStageData::parentCoil;
+  %ignore openstudio::model::CoilHeatingElectricMultiStageStageData::parentCoil;
 
 #endif
 
@@ -84,6 +86,8 @@ namespace model {
 %feature("valuewrapper") WaterUseConnections;
 %feature("valuewrapper") CoilCoolingDXMultiSpeed;
 %feature("valuewrapper") CoilHeatingDXMultiSpeed;
+%feature("valuewrapper") CoilHeatingGasMultiStage;
+%feature("valuewrapper") CoilHeatingElectricMultiStage;
 
 class AirflowNetworkDistributionNode;
 class AirflowNetworkZone;
@@ -96,6 +100,8 @@ class PlantEquipmentOperationCoolingLoad;
 class WaterUseConnections;
 class CoilCoolingDXMultiSpeed;
 class CoilHeatingDXMultiSpeed;
+class CoilHeatingGasMultiStage;
+class CoilHeatingElectricMultiStage;
 
 }
 }
@@ -176,6 +182,7 @@ MODELOBJECT_TEMPLATES(CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFitSpeed
 MODELOBJECT_TEMPLATES(CoilHeatingDXMultiSpeedStageData);
 MODELOBJECT_TEMPLATES(CoilHeatingDXVariableRefrigerantFlow);
 MODELOBJECT_TEMPLATES(CoilHeatingGasMultiStageStageData);
+MODELOBJECT_TEMPLATES(CoilHeatingElectricMultiStageStageData);
 MODELOBJECT_TEMPLATES(CoilHeatingDXVariableSpeedSpeedData);
 MODELOBJECT_TEMPLATES(CoilHeatingWater);
 MODELOBJECT_TEMPLATES(CoilHeatingWaterToAirHeatPumpEquationFit);
@@ -297,6 +304,7 @@ SWIG_MODELOBJECT(CoilCoolingWaterToAirHeatPumpVariableSpeedEquationFitSpeedData,
 SWIG_MODELOBJECT(CoilHeatingDXMultiSpeedStageData, 1);
 SWIG_MODELOBJECT(CoilHeatingDXVariableRefrigerantFlow, 1);
 SWIG_MODELOBJECT(CoilHeatingGasMultiStageStageData, 1);
+SWIG_MODELOBJECT(CoilHeatingElectricMultiStageStageData, 1);
 SWIG_MODELOBJECT(CoilHeatingDXVariableSpeedSpeedData, 1);
 SWIG_MODELOBJECT(CoilHeatingWater, 1);
 SWIG_MODELOBJECT(CoilHeatingWaterToAirHeatPumpEquationFit, 1);
diff --git a/src/model/ModelStraightComponent.i b/src/model/ModelStraightComponent.i
index 16361e2fb83..af3f20877ca 100644
--- a/src/model/ModelStraightComponent.i
+++ b/src/model/ModelStraightComponent.i
@@ -131,6 +131,7 @@ MODELOBJECT_TEMPLATES(CoilHeatingDXMultiSpeed);
 MODELOBJECT_TEMPLATES(CoilHeatingDXSingleSpeed);
 MODELOBJECT_TEMPLATES(CoilHeatingDXVariableSpeed);
 MODELOBJECT_TEMPLATES(CoilHeatingElectric);
+MODELOBJECT_TEMPLATES(CoilHeatingElectricMultiStage);
 MODELOBJECT_TEMPLATES(CoilHeatingFourPipeBeam);
 MODELOBJECT_TEMPLATES(CoilHeatingGas);
 MODELOBJECT_TEMPLATES(CoilHeatingGasMultiStage);
@@ -214,6 +215,7 @@ SWIG_MODELOBJECT(CoilHeatingDXMultiSpeed,1);
 SWIG_MODELOBJECT(CoilHeatingDXSingleSpeed,1);
 SWIG_MODELOBJECT(CoilHeatingDXVariableSpeed,1);
 SWIG_MODELOBJECT(CoilHeatingElectric,1);
+SWIG_MODELOBJECT(CoilHeatingElectricMultiStage,1);
 SWIG_MODELOBJECT(CoilHeatingFourPipeBeam, 1);
 SWIG_MODELOBJECT(CoilHeatingGas,1);
 SWIG_MODELOBJECT(CoilHeatingGasMultiStage,1);
@@ -297,6 +299,14 @@ SWIG_MODELOBJECT(WaterUseConnections,1);
         OptionalCoilHeatingDXMultiSpeed parentCoil(const openstudio::model::CoilHeatingDXMultiSpeedStageData& stage){
           return stage.parentCoil();
         }
+
+        OptionalCoilHeatingGasMultiStage parentCoil(const openstudio::model::OptionalCoilHeatingGasMultiStageStageData& stage){
+          return stage.parentCoil();
+        }
+
+        OptionalCoilHeatingElectricMultiStage parentCoil(const openstudio::model::OptionalCoilHeatingElectricMultiStageStageData& stage){
+          return stage.parentCoil();
+        }
       }
     }
   }
@@ -337,6 +347,20 @@ SWIG_MODELOBJECT(WaterUseConnections,1);
       }
     }
 
+    public partial class CoilHeatingGasMultiStage : ParentObject
+    {
+      public OptionalCoilHeatingGasMultiStage parentCoil() {
+        return OpenStudio.OpenStudioModelStraightComponent.parentCoil(this);
+      }
+    }
+
+    public partial class CoilHeatingElectricMultiStage : ParentObject
+    {
+      public OptionalCoilHeatingElectricMultiStage parentCoil() {
+        return OpenStudio.OpenStudioModelStraightComponent.parentCoil(this);
+      }
+    }
+
   %}
 #endif
 #endif
diff --git a/src/model/ScheduleTypeRegistry.cpp b/src/model/ScheduleTypeRegistry.cpp
index 8a644944c55..0057275c05b 100644
--- a/src/model/ScheduleTypeRegistry.cpp
+++ b/src/model/ScheduleTypeRegistry.cpp
@@ -208,6 +208,7 @@ namespace model {
       {"CoilHeatingDXSingleSpeed", "Availability", "availabilitySchedule", false, "Availability", 0.0, 1.0},
       {"CoilHeatingDXVariableRefrigerantFlow", "Availability Schedule", "availabilitySchedule", false, "Availability", 0.0, 1.0},
       {"CoilHeatingElectric", "Availability", "availabilitySchedule", false, "Availability", 0.0, 1.0},
+      {"CoilHeatingElectricMultiStage", "Availability Schedule", "availabilitySchedule", false, "Availability", 0.0, 1.0},
       {"CoilHeatingGas", "Availability", "availabilitySchedule", false, "Availability", 0.0, 1.0},
       {"CoilHeatingGasMultiStage", "Availability Schedule", "availabilitySchedule", false, "Availability", 0.0, 1.0},
       {"CoilHeatingLowTempRadiantConstFlow", "Heating High Water Temperature", "heatingHighWaterTemperatureSchedule", true, "Temperature",
diff --git a/src/osversion/VersionTranslator.cpp b/src/osversion/VersionTranslator.cpp
index 8971c7e977a..dba8698cef7 100644
--- a/src/osversion/VersionTranslator.cpp
+++ b/src/osversion/VersionTranslator.cpp
@@ -7319,6 +7319,57 @@ namespace osversion {
         m_refactored.push_back(RefactoredObjectData(object, newObject));
         ss << newObject;
 
+      } else if (iddname == "OS:Coil:Heating:Gas:MultiStage") {
+
+        // 1 Field was made required:
+        // ------------------------------------------------
+        // * Availability Schedule * 2
+
+        auto iddObject = idd_3_5_0.getObject(iddname);
+        IdfObject newObject(iddObject.get());
+
+        if (object.isEmpty(2)) {
+          // Add an always on discrete schedule if one does not already exist
+          boost::optional<IdfObject> alwaysOnSchedule;
+          for (const IdfObject& object : idf_3_4_0.objects()) {
+            if (object.iddObject().name() == "OS:Schedule:Constant") {
+              if (boost::optional<std::string> name = object.getString(1)) {
+                if (istringEqual(name.get(), "Always On Discrete")) {
+                  if (boost::optional<double> value = object.getDouble(3)) {
+                    if (equal<double>(value.get(), 1.0)) {
+                      alwaysOnSchedule = object;
+                    }
+                  }
+                }
+              }
+            }
+          }
+
+          if (!alwaysOnSchedule) {
+            alwaysOnSchedule = IdfObject(idd_3_5_0.getObject("OS:Schedule:Constant").get());
+            alwaysOnSchedule->setString(0, toString(createUUID()));
+            alwaysOnSchedule->setString(1, "Always On Discrete");
+            alwaysOnSchedule->setDouble(3, 1.0);
+            IdfObject typeLimits(idd_3_5_0.getObject("OS:ScheduleTypeLimits").get());
+            typeLimits.setString(0, toString(createUUID()));
+            typeLimits.setString(1, "Always On Discrete Limits");
+            typeLimits.setDouble(2, 0.0);
+            typeLimits.setDouble(3, 1.0);
+            typeLimits.setString(4, "Discrete");
+            typeLimits.setString(5, "Availability");
+            alwaysOnSchedule->setString(2, typeLimits.getString(0).get());
+            ss << alwaysOnSchedule.get();
+            ss << typeLimits;
+            m_new.push_back(alwaysOnSchedule.get());
+            m_new.push_back(typeLimits);
+          }
+
+          newObject.setString(2, alwaysOnSchedule->getString(0).get());
+        }
+
+        m_refactored.push_back(RefactoredObjectData(object, newObject));
+        ss << newObject;
+
         // No-op
       } else {
         ss << object;


Test patch:
diff --git a/src/model/test/CoilHeatingElectricMultiStageStageData_GTest.cpp b/src/model/test/CoilHeatingElectricMultiStageStageData_GTest.cpp
new file mode 100644
index 00000000000..795fd199de4
--- /dev/null
+++ b/src/model/test/CoilHeatingElectricMultiStageStageData_GTest.cpp
@@ -0,0 +1,122 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include <gtest/gtest.h>
+
+#include "ModelFixture.hpp"
+
+#include "../CoilHeatingElectricMultiStageStageData.hpp"
+#include "../CoilHeatingElectricMultiStageStageData_Impl.hpp"
+#include "../CoilHeatingElectricMultiStage.hpp"
+#include "../CoilHeatingElectricMultiStage_Impl.hpp"
+
+using namespace openstudio;
+using namespace openstudio::model;
+
+TEST_F(ModelFixture, CoilHeatingElectricMultiStageStageData_GettersSetters) {
+  Model m;
+  CoilHeatingElectricMultiStage coil(m);
+  CoilHeatingElectricMultiStageStageData stageData1(m);
+  CoilHeatingElectricMultiStageStageData stageData2(m);
+
+  EXPECT_EQ(0u, coil.stages().size());
+  EXPECT_FALSE(stageData1.parentCoil());
+  EXPECT_TRUE(coil.addStage(stageData1));
+  EXPECT_TRUE(coil.addStage(stageData2));
+  EXPECT_TRUE(stageData1.parentCoil());
+  EXPECT_EQ(2u, coil.stages().size());
+
+  EXPECT_EQ(1.0, stageData1.efficiency());
+  EXPECT_FALSE(stageData1.nominalCapacity());
+  EXPECT_TRUE(stageData1.isNominalCapacityAutosized());
+
+  EXPECT_TRUE(stageData1.setEfficiency(0.75));
+  EXPECT_TRUE(stageData1.setNominalCapacity(1000.0));
+
+  EXPECT_EQ(0.75, stageData1.efficiency());
+  ASSERT_TRUE(stageData1.nominalCapacity());
+  EXPECT_EQ(1000.0, stageData1.nominalCapacity().get());
+  EXPECT_FALSE(stageData1.isNominalCapacityAutosized());
+
+  stageData1.autosizeNominalCapacity();
+  EXPECT_FALSE(stageData1.nominalCapacity());
+  EXPECT_TRUE(stageData1.isNominalCapacityAutosized());
+}
+
+TEST_F(ModelFixture, CoilHeatingElectricMultiStageStageData_remove) {
+  // If we remove a stage, we would like any CoilHeatingElectricMultiStage that use it to have their extensible groups adjusted
+  Model model;
+
+  CoilHeatingElectricMultiStageStageData stage1(model);
+  CoilHeatingElectricMultiStageStageData stage2(model);
+
+  CoilHeatingElectricMultiStage coil(model);
+  EXPECT_TRUE(coil.addStage(stage1));
+  EXPECT_TRUE(coil.addStage(stage2));
+
+  EXPECT_EQ(2, coil.numExtensibleGroups());
+  EXPECT_EQ(2, coil.numberOfStages());
+  EXPECT_EQ(2, coil.stages().size());
+
+  EXPECT_EQ(2, model.getConcreteModelObjects<CoilHeatingElectricMultiStageStageData>().size());
+
+  stage1.remove();
+
+  EXPECT_EQ(1, model.getConcreteModelObjects<CoilHeatingElectricMultiStageStageData>().size());
+
+  EXPECT_EQ(1, coil.numExtensibleGroups());
+  EXPECT_EQ(1, coil.numberOfStages());
+  EXPECT_EQ(1, coil.stages().size());
+
+  EXPECT_EQ(stage2, coil.stages().front());
+}
+
+TEST_F(ModelFixture, CoilHeatingElectricMultiStageStageData_Unicity) {
+  Model model;
+
+  CoilHeatingElectricMultiStageStageData stage(model);
+  EXPECT_FALSE(stage.parentCoil());
+
+  CoilHeatingElectricMultiStage coil1(model);
+  EXPECT_TRUE(coil1.addStage(stage));
+  ASSERT_TRUE(stage.parentCoil());
+  EXPECT_EQ(coil1, stage.parentCoil().get());
+
+  // StageData is already used, so refuse
+  CoilHeatingElectricMultiStage coil2(model);
+  EXPECT_FALSE(coil2.addStage(stage));
+  EXPECT_EQ(1u, model.getConcreteModelObjects<CoilHeatingElectricMultiStageStageData>().size());
+
+  EXPECT_EQ(1u, coil1.numExtensibleGroups());
+  EXPECT_EQ(1u, coil1.numberOfStages());
+  EXPECT_EQ(1u, coil1.stages().size());
+  EXPECT_EQ(0u, coil2.numExtensibleGroups());
+  EXPECT_EQ(0u, coil2.numberOfStages());
+  EXPECT_EQ(0u, coil2.stages().size());
+}
diff --git a/src/model/test/CoilHeatingElectricMultiStage_GTest.cpp b/src/model/test/CoilHeatingElectricMultiStage_GTest.cpp
new file mode 100644
index 00000000000..cabf77635e1
--- /dev/null
+++ b/src/model/test/CoilHeatingElectricMultiStage_GTest.cpp
@@ -0,0 +1,265 @@
+/***********************************************************************************************************************
+*  OpenStudio(R), Copyright (c) 2008-2022, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
+*
+*  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+*  following conditions are met:
+*
+*  (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+*  disclaimer.
+*
+*  (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+*  disclaimer in the documentation and/or other materials provided with the distribution.
+*
+*  (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
+*  derived from this software without specific prior written permission from the respective party.
+*
+*  (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
+*  may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
+*  written permission from Alliance for Sustainable Energy, LLC.
+*
+*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+*  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
+*  STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+*  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+***********************************************************************************************************************/
+
+#include <gtest/gtest.h>
+
+#include "ModelFixture.hpp"
+
+#include "../CoilHeatingElectricMultiStage.hpp"
+#include "../CoilHeatingElectricMultiStage_Impl.hpp"
+#include "../CoilHeatingElectricMultiStageStageData.hpp"
+#include "../CoilHeatingElectricMultiStageStageData_Impl.hpp"
+#include "../Schedule.hpp"
+#include "../Schedule_Impl.hpp"
+#include "../ScheduleConstant.hpp"
+#include "../ScheduleConstant_Impl.hpp"
+#include "../ScheduleTypeLimits.hpp"
+#include "../ScheduleTypeLimits_Impl.hpp"
+#include "../AirLoopHVAC.hpp"
+#include "../Node.hpp"
+
+using namespace openstudio;
+using namespace openstudio::model;
+
+TEST_F(ModelFixture, CoilHeatingElectricMultiStage_GettersSetters) {
+  Model m;
+  CoilHeatingElectricMultiStage coil(m);
+
+  auto alwaysOn = m.alwaysOnDiscreteSchedule();
+  EXPECT_EQ(alwaysOn, coil.availabilitySchedule());
+  EXPECT_EQ(0, coil.stages().size());
+
+  ScheduleConstant scheduleConstant(m);
+  EXPECT_TRUE(coil.setAvailabilitySchedule(scheduleConstant));
+
+  EXPECT_EQ(scheduleConstant, coil.availabilitySchedule());
+}
+
+TEST_F(ModelFixture, CoilHeatingElectricMultiStage_addToNode) {
+  // Should not be allowed, only meant to be inside a Unitary
+  Model m;
+
+  CoilHeatingElectricMultiStage h(m);
+  CoilHeatingElectricMultiStageStageData stageData(m);
+
+  AirLoopHVAC a(m);
+  Node supplyOutlet = a.supplyOutletNode();
+  EXPECT_FALSE(h.addToNode(supplyOutlet));
+}
+
+TEST_F(ModelFixture, CoilHeatingElectricMultiStage_Stages) {
+  Model model;
+  CoilHeatingElectricMultiStage coil(model);
+
+  std::vector<CoilHeatingElectricMultiStageStageData> stages;
+  for (unsigned i = 1; i <= 4; ++i) {
+    CoilHeatingElectricMultiStageStageData stage(model);
+    stage.setName("Stage " + std::to_string(i));
+    stages.push_back(stage);
+    EXPECT_TRUE(coil.addStage(stage));
+    EXPECT_EQ(i, coil.numberOfStages());
+    EXPECT_EQ(stages, coil.stages());
+  }
+
+  // Can't add more than 4 Stages;
+  CoilHeatingElectricMultiStageStageData anotherStage(model);
+  EXPECT_FALSE(coil.addStage(anotherStage));
+  EXPECT_EQ(4, coil.numberOfStages());
+  EXPECT_EQ(stages, coil.stages());
+
+  // Can't remove a stage that's not in there...
+  EXPECT_FALSE(coil.removeStage(anotherStage));
+  EXPECT_EQ(4, coil.numberOfStages());
+  EXPECT_EQ(stages, coil.stages());
+
+  {
+    int stageIndex = 3;
+    std::vector<CoilHeatingElectricMultiStageStageData> thisStages = coil.stages();
+    const auto& stageAtIndex = thisStages[stageIndex - 1];
+    EXPECT_TRUE(std::find(thisStages.begin(), thisStages.end(), stageAtIndex) != thisStages.end());
+    auto optIndex = coil.stageIndex(stageAtIndex);
+    ASSERT_TRUE(optIndex);
+    EXPECT_EQ(stageIndex, optIndex.get());
+    EXPECT_TRUE(coil.removeStage(stageIndex));
+    EXPECT_EQ(3, coil.numberOfStages());
+    thisStages = coil.stages();
+    EXPECT_FALSE(std::find(thisStages.begin(), thisStages.end(), stageAtIndex) != thisStages.end());
+    // Do the same on our vector, so we're up to date...
+    stages.erase(stages.begin() + stageIndex - 1);
+    EXPECT_EQ(stages, coil.stages());
+  }
+
+  {
+    int stageIndex = 2;
+    std::vector<CoilHeatingElectricMultiStageStageData> thisStages = coil.stages();
+    const auto& stageAtIndex = thisStages[stageIndex - 1];
+    EXPECT_TRUE(std::find(thisStages.begin(), thisStages.end(), stageAtIndex) != thisStages.end());
+    auto optIndex = coil.stageIndex(stageAtIndex);
+    ASSERT_TRUE(optIndex);
+    EXPECT_EQ(stageIndex, optIndex.get());
+    EXPECT_TRUE(coil.removeStage(stageAtIndex));
+    EXPECT_EQ(2, coil.numberOfStages());
+    thisStages = coil.stages();
+    EXPECT_FALSE(std::find(thisStages.begin(), thisStages.end(), stageAtIndex) != thisStages.end());
+    // Do the same on our vector, so we're up to date...
+    stages.erase(std::find(stages.begin(), stages.end(), stageAtIndex));
+    EXPECT_EQ(stages, coil.stages());
+  }
+
+  coil.removeAllStages();
+  EXPECT_EQ(0, coil.numberOfStages());
+
+  EXPECT_TRUE(coil.setStages(stages));
+  EXPECT_EQ(2, coil.numberOfStages());
+  EXPECT_EQ(stages, coil.stages());
+
+  for (unsigned i = 5; i <= 7; ++i) {
+    CoilHeatingElectricMultiStageStageData stage(model);
+    coil.setName("Stage " + std::to_string(i));
+    stages.push_back(stage);
+  }
+  EXPECT_EQ(5u, stages.size());
+  coil.removeAllStages();
+  EXPECT_TRUE(coil.addStage(anotherStage));
+
+  // This should clear, then assign the first 4, but then return false since the 5th failed
+  EXPECT_FALSE(coil.setStages(stages));
+  EXPECT_EQ(4, coil.numberOfStages());
+  {
+    std::vector<CoilHeatingElectricMultiStageStageData> thisStages = coil.stages();
+    for (unsigned i = 0; i < 4; ++i) {
+      EXPECT_EQ(stages[i], thisStages[i]);
+    }
+  }
+  stages.pop_back();
+  EXPECT_EQ(4u, stages.size());
+
+  {
+    const auto& stageAtEnd = stages.back();
+    auto optIndex = coil.stageIndex(stageAtEnd);
+    ASSERT_TRUE(optIndex);
+    EXPECT_EQ(coil.numberOfStages(), optIndex.get());
+
+    EXPECT_TRUE(coil.setStageIndex(stageAtEnd, 2));
+    std::vector<CoilHeatingElectricMultiStageStageData> thisStages = coil.stages();
+    optIndex = coil.stageIndex(stageAtEnd);
+    ASSERT_TRUE(optIndex);
+    EXPECT_EQ(2, optIndex.get());
+    EXPECT_EQ(4, coil.numberOfStages());
+    for (unsigned i = 1; i <= coil.numberOfStages(); ++i) {
+      if (i < optIndex.get()) {
+        EXPECT_EQ(stages[i - 1], coil.stages()[i - 1]);
+      } else if (i > optIndex.get()) {
+        EXPECT_EQ(stages[i - 2], coil.stages()[i - 1]);
+      }
+    }
+  }
+
+  coil.removeAllStages();
+  EXPECT_EQ(0u, coil.numExtensibleGroups());
+  EXPECT_EQ(0u, coil.numberOfStages());
+  EXPECT_EQ(0u, coil.stages().size());
+
+  // Test that added a stage from another model will fail but not add a blank extensible group
+  Model model2;
+  CoilHeatingElectricMultiStageStageData stageFromAnotherModel(model2);
+  EXPECT_FALSE(coil.addStage(stageFromAnotherModel));
+  EXPECT_EQ(0u, coil.numExtensibleGroups());
+  EXPECT_EQ(0u, coil.numberOfStages());
+  EXPECT_EQ(0u, coil.stages().size());
+}
+
+TEST_F(ModelFixture, CoilHeatingElectricMultiStage_Remove) {
+  Model m;
+
+  CoilHeatingElectricMultiStage coil(m);
+  CoilHeatingElectricMultiStageStageData stage(m);
+  EXPECT_TRUE(coil.addStage(stage));
+  EXPECT_EQ(1, m.getConcreteModelObjects<CoilHeatingElectricMultiStage>().size());
+  EXPECT_EQ(1, m.getConcreteModelObjects<CoilHeatingElectricMultiStageStageData>().size());
+  coil.remove();
+
+  EXPECT_EQ(0, m.getConcreteModelObjects<CoilHeatingElectricMultiStage>().size());
+  EXPECT_EQ(0, m.getConcreteModelObjects<CoilHeatingElectricMultiStageStageData>().size());
+  auto schedules = m.getModelObjects<model::ScheduleConstant>();
+  auto limits = m.getModelObjects<model::ScheduleTypeLimits>();
+  EXPECT_EQ(schedules.size() + limits.size(), m.modelObjects().size());
+}
+
+TEST_F(ModelFixture, CoilHeatingElectricMultiStage_Clone) {
+  // Ref #4663
+  Model m;
+  CoilHeatingElectricMultiStage coil(m);
+
+  std::vector<CoilHeatingElectricMultiStageStageData> stages{CoilHeatingElectricMultiStageStageData{m}, CoilHeatingElectricMultiStageStageData{m}};
+  EXPECT_TRUE(coil.setStages(stages));
+
+  EXPECT_EQ(2, coil.stages().size());
+  EXPECT_EQ(2, coil.numberOfStages());
+  EXPECT_EQ(1, m.getConcreteModelObjects<CoilHeatingElectricMultiStage>().size());
+  EXPECT_EQ(2, m.getConcreteModelObjects<CoilHeatingElectricMultiStageStageData>().size());
+
+  {
+    // Clone in another model first, so we don't affect the original one. Cloning also clones the stages
+    Model m2;
+    auto coilClone = coil.clone(m2).cast<CoilHeatingElectricMultiStage>();
+    std::vector<CoilHeatingElectricMultiStageStageData> stages2 = coilClone.stages();
+    EXPECT_EQ(2, stages2.size());
+    EXPECT_EQ(1, m2.getConcreteModelObjects<CoilHeatingElectricMultiStage>().size());
+    EXPECT_EQ(2, m2.getConcreteModelObjects<CoilHeatingElectricMultiStageStageData>().size());
+    for (auto& stage : stages) {
+      EXPECT_TRUE(std::find_if(stages2.cbegin(), stages2.cend(), [&stage](const auto& s) { return s.handle() == stage.handle(); }) == stages2.cend())
+        << "Did not expect to find, in the cloned Coil, the CoilHeatingElectricMultiStageStageData '" << stage.nameString() << "'";
+    }
+
+    coilClone.remove();
+    EXPECT_EQ(0, m2.getConcreteModelObjects<CoilHeatingElectricMultiStage>().size());
+    EXPECT_EQ(0, m2.getConcreteModelObjects<CoilHeatingElectricMultiStageStageData>().size());
+  }
+
+  {
+    // Same model: we expect stages to be cloned and reassigned to the clone
+    auto coilClone = coil.clone(m).cast<CoilHeatingElectricMultiStage>();
+    std::vector<CoilHeatingElectricMultiStageStageData> stages2 = coilClone.stages();
+    EXPECT_EQ(2, stages2.size());
+    EXPECT_EQ(2, m.getConcreteModelObjects<CoilHeatingElectricMultiStage>().size());
+    EXPECT_EQ(4, m.getConcreteModelObjects<CoilHeatingElectricMultiStageStageData>().size());
+    for (auto& stage : stages) {
+      EXPECT_TRUE(std::find_if(stages2.cbegin(), stages2.cend(), [&stage](const auto& s) { return s.handle() == stage.handle(); }) == stages2.cend())
+        << "Did not expect to find, in the cloned Coil, the CoilHeatingElectricMultiStageStageData '" << stage.nameString() << "'";
+    }
+    coil.remove();
+    EXPECT_EQ(1, m.getConcreteModelObjects<CoilHeatingElectricMultiStage>().size());
+    EXPECT_EQ(2, m.getConcreteModelObjects<CoilHeatingElectricMultiStageStageData>().size());
+
+    coilClone.remove();
+    EXPECT_EQ(0, m.getConcreteModelObjects<CoilHeatingElectricMultiStage>().size());
+    EXPECT_EQ(0, m.getConcreteModelObjects<CoilHeatingElectricMultiStageStageData>().size());
+  }
+}
diff --git a/src/model/test/CoilHeatingGasMultiStageStageData_GTest.cpp b/src/model/test/CoilHeatingGasMultiStageStageData_GTest.cpp
index 1df1a1fda53..4d7d4882d01 100644
--- a/src/model/test/CoilHeatingGasMultiStageStageData_GTest.cpp
+++ b/src/model/test/CoilHeatingGasMultiStageStageData_GTest.cpp
@@ -33,6 +33,93 @@
 
 #include "../CoilHeatingGasMultiStageStageData.hpp"
 #include "../CoilHeatingGasMultiStageStageData_Impl.hpp"
+#include "../CoilHeatingGasMultiStage.hpp"
+#include "../CoilHeatingGasMultiStage_Impl.hpp"
 
 using namespace openstudio;
 using namespace openstudio::model;
+
+TEST_F(ModelFixture, CoilHeatingGasMultiStageStageData_GettersSetters) {
+  Model m;
+  CoilHeatingGasMultiStage coil(m);
+  CoilHeatingGasMultiStageStageData stageData1(m);
+  CoilHeatingGasMultiStageStageData stageData2(m);
+
+  EXPECT_EQ(0u, coil.stages().size());
+  EXPECT_FALSE(stageData1.parentCoil());
+  EXPECT_TRUE(coil.addStage(stageData1));
+  EXPECT_TRUE(coil.addStage(stageData2));
+  EXPECT_TRUE(stageData1.parentCoil());
+  EXPECT_EQ(2u, coil.stages().size());
+
+  EXPECT_EQ(0.8, stageData1.gasBurnerEfficiency());
+  EXPECT_FALSE(stageData1.nominalCapacity());
+  EXPECT_TRUE(stageData1.isNominalCapacityAutosized());
+  EXPECT_EQ(0.0, stageData1.parasiticElectricLoad());
+
+  EXPECT_TRUE(stageData1.setGasBurnerEfficiency(0.75));
+  EXPECT_TRUE(stageData1.setNominalCapacity(1000.0));
+  EXPECT_TRUE(stageData1.setParasiticElectricLoad(1.5));
+
+  EXPECT_EQ(0.75, stageData1.gasBurnerEfficiency());
+  ASSERT_TRUE(stageData1.nominalCapacity());
+  EXPECT_EQ(1000.0, stageData1.nominalCapacity().get());
+  EXPECT_FALSE(stageData1.isNominalCapacityAutosized());
+  EXPECT_EQ(1.5, stageData1.parasiticElectricLoad());
+
+  stageData1.autosizeNominalCapacity();
+  EXPECT_FALSE(stageData1.nominalCapacity());
+  EXPECT_TRUE(stageData1.isNominalCapacityAutosized());
+}
+
+TEST_F(ModelFixture, CoilHeatingGasMultiStageStageData_remove) {
+  // If we remove a stage, we would like any CoilHeatingGasMultiStage that use it to have their extensible groups adjusted
+  Model model;
+
+  CoilHeatingGasMultiStageStageData stage1(model);
+  CoilHeatingGasMultiStageStageData stage2(model);
+
+  CoilHeatingGasMultiStage coil(model);
+  EXPECT_TRUE(coil.addStage(stage1));
+  EXPECT_TRUE(coil.addStage(stage2));
+
+  EXPECT_EQ(2, coil.numExtensibleGroups());
+  EXPECT_EQ(2, coil.numberOfStages());
+  EXPECT_EQ(2, coil.stages().size());
+
+  EXPECT_EQ(2, model.getConcreteModelObjects<CoilHeatingGasMultiStageStageData>().size());
+
+  stage1.remove();
+
+  EXPECT_EQ(1, model.getConcreteModelObjects<CoilHeatingGasMultiStageStageData>().size());
+
+  EXPECT_EQ(1, coil.numExtensibleGroups());
+  EXPECT_EQ(1, coil.numberOfStages());
+  EXPECT_EQ(1, coil.stages().size());
+
+  EXPECT_EQ(stage2, coil.stages().front());
+}
+
+TEST_F(ModelFixture, CoilHeatingGasMultiStageStageData_Unicity) {
+  Model model;
+
+  CoilHeatingGasMultiStageStageData stage(model);
+  EXPECT_FALSE(stage.parentCoil());
+
+  CoilHeatingGasMultiStage coil1(model);
+  EXPECT_TRUE(coil1.addStage(stage));
+  ASSERT_TRUE(stage.parentCoil());
+  EXPECT_EQ(coil1, stage.parentCoil().get());
+
+  // StageData is already used, so refuse
+  CoilHeatingGasMultiStage coil2(model);
+  EXPECT_FALSE(coil2.addStage(stage));
+  EXPECT_EQ(1u, model.getConcreteModelObjects<CoilHeatingGasMultiStageStageData>().size());
+
+  EXPECT_EQ(1u, coil1.numExtensibleGroups());
+  EXPECT_EQ(1u, coil1.numberOfStages());
+  EXPECT_EQ(1u, coil1.stages().size());
+  EXPECT_EQ(0u, coil2.numExtensibleGroups());
+  EXPECT_EQ(0u, coil2.numberOfStages());
+  EXPECT_EQ(0u, coil2.stages().size());
+}
diff --git a/src/model/test/CoilHeatingGasMultiStage_GTest.cpp b/src/model/test/CoilHeatingGasMultiStage_GTest.cpp
index fa7b47ac6fd..11018ccb133 100644
--- a/src/model/test/CoilHeatingGasMultiStage_GTest.cpp
+++ b/src/model/test/CoilHeatingGasMultiStage_GTest.cpp
@@ -33,6 +33,251 @@
 
 #include "../CoilHeatingGasMultiStage.hpp"
 #include "../CoilHeatingGasMultiStage_Impl.hpp"
+#include "../CoilHeatingGasMultiStageStageData.hpp"
+#include "../CoilHeatingGasMultiStageStageData_Impl.hpp"
+#include "../Schedule.hpp"
+#include "../Schedule_Impl.hpp"
+#include "../ScheduleConstant.hpp"
+#include "../ScheduleConstant_Impl.hpp"
+#include "../ScheduleTypeLimits.hpp"
+#include "../ScheduleTypeLimits_Impl.hpp"
+#include "../CurveQuadratic.hpp"
+#include "../CurveQuadratic_Impl.hpp"
+#include "../AirLoopHVAC.hpp"
+#include "../Node.hpp"
 
 using namespace openstudio;
 using namespace openstudio::model;
+
+TEST_F(ModelFixture, CoilHeatingGasMultiStage_GettersSetters) {
+  Model m;
+  CoilHeatingGasMultiStage coil(m);
+
+  auto alwaysOn = m.alwaysOnDiscreteSchedule();
+  EXPECT_EQ(alwaysOn, coil.availabilitySchedule());
+  EXPECT_FALSE(coil.partLoadFractionCorrelationCurve());
+  EXPECT_FALSE(coil.parasiticGasLoad());
+  EXPECT_EQ(0u, coil.stages().size());
+
+  ScheduleConstant scheduleConstant(m);
+  EXPECT_TRUE(coil.setAvailabilitySchedule(scheduleConstant));
+  CurveQuadratic curveQuadratic(m);
+  EXPECT_TRUE(coil.setPartLoadFractionCorrelationCurve(curveQuadratic));
+  EXPECT_TRUE(coil.setParasiticGasLoad(100.0));
+
+  EXPECT_EQ(scheduleConstant, coil.availabilitySchedule());
+  ASSERT_TRUE(coil.partLoadFractionCorrelationCurve());
+  EXPECT_EQ(curveQuadratic.handle(), coil.partLoadFractionCorrelationCurve().get().handle());
+  ASSERT_TRUE(coil.parasiticGasLoad());
+  EXPECT_EQ(100.0, coil.parasiticGasLoad().get());
+
+  coil.resetPartLoadFractionCorrelationCurve();
+  coil.resetParasiticGasLoad();
+
+  EXPECT_FALSE(coil.partLoadFractionCorrelationCurve());
+  EXPECT_FALSE(coil.parasiticGasLoad());
+  EXPECT_EQ(0u, coil.stages().size());
+}
+
+TEST_F(ModelFixture, CoilHeatingGasMultiStage_addToNode) {
+  // Should not be allowed, only meant to be inside a Unitary
+  Model m;
+
+  CoilHeatingGasMultiStage h(m);
+  CoilHeatingGasMultiStageStageData stageData(m);
+
+  AirLoopHVAC a(m);
+  Node supplyOutlet = a.supplyOutletNode();
+  EXPECT_FALSE(h.addToNode(supplyOutlet));
+}
+
+TEST_F(ModelFixture, CoilHeatingGasMultiStage_Stages) {
+  Model model;
+  CoilHeatingGasMultiStage coil(model);
+
+  std::vector<CoilHeatingGasMultiStageStageData> stages;
+  for (unsigned i = 1; i <= 4; ++i) {
+    CoilHeatingGasMultiStageStageData stage(model);
+    stage.setName("Stage " + std::to_string(i));
+    stages.push_back(stage);
+    EXPECT_TRUE(coil.addStage(stage));
+    EXPECT_EQ(i, coil.numberOfStages());
+    EXPECT_EQ(stages, coil.stages());
+  }
+
+  // Can't add more than 4 Stages;
+  CoilHeatingGasMultiStageStageData anotherStage(model);
+  EXPECT_FALSE(coil.addStage(anotherStage));
+  EXPECT_EQ(4, coil.numberOfStages());
+  EXPECT_EQ(stages, coil.stages());
+
+  // Can't remove a stage that's not in there...
+  EXPECT_FALSE(coil.removeStage(anotherStage));
+  EXPECT_EQ(4, coil.numberOfStages());
+  EXPECT_EQ(stages, coil.stages());
+
+  {
+    int stageIndex = 3;
+    std::vector<CoilHeatingGasMultiStageStageData> thisStages = coil.stages();
+    const auto& stageAtIndex = thisStages[stageIndex - 1];
+    EXPECT_TRUE(std::find(thisStages.begin(), thisStages.end(), stageAtIndex) != thisStages.end());
+    auto optIndex = coil.stageIndex(stageAtIndex);
+    ASSERT_TRUE(optIndex);
+    EXPECT_EQ(stageIndex, optIndex.get());
+    EXPECT_TRUE(coil.removeStage(stageIndex));
+    EXPECT_EQ(3, coil.numberOfStages());
+    thisStages = coil.stages();
+    EXPECT_FALSE(std::find(thisStages.begin(), thisStages.end(), stageAtIndex) != thisStages.end());
+    // Do the same on our vector, so we're up to date...
+    stages.erase(stages.begin() + stageIndex - 1);
+    EXPECT_EQ(stages, coil.stages());
+  }
+
+  {
+    int stageIndex = 2;
+    std::vector<CoilHeatingGasMultiStageStageData> thisStages = coil.stages();
+    const auto& stageAtIndex = thisStages[stageIndex - 1];
+    EXPECT_TRUE(std::find(thisStages.begin(), thisStages.end(), stageAtIndex) != thisStages.end());
+    auto optIndex = coil.stageIndex(stageAtIndex);
+    ASSERT_TRUE(optIndex);
+    EXPECT_EQ(stageIndex, optIndex.get());
+    EXPECT_TRUE(coil.removeStage(stageAtIndex));
+    EXPECT_EQ(2, coil.numberOfStages());
+    thisStages = coil.stages();
+    EXPECT_FALSE(std::find(thisStages.begin(), thisStages.end(), stageAtIndex) != thisStages.end());
+    // Do the same on our vector, so we're up to date...
+    stages.erase(std::find(stages.begin(), stages.end(), stageAtIndex));
+    EXPECT_EQ(stages, coil.stages());
+  }
+
+  coil.removeAllStages();
+  EXPECT_EQ(0, coil.numberOfStages());
+
+  EXPECT_TRUE(coil.setStages(stages));
+  EXPECT_EQ(2, coil.numberOfStages());
+  EXPECT_EQ(stages, coil.stages());
+
+  for (unsigned i = 5; i <= 7; ++i) {
+    CoilHeatingGasMultiStageStageData stage(model);
+    coil.setName("Stage " + std::to_string(i));
+    stages.push_back(stage);
+  }
+  EXPECT_EQ(5u, stages.size());
+  coil.removeAllStages();
+  EXPECT_TRUE(coil.addStage(anotherStage));
+
+  // This should clear, then assign the first 4, but then return false since the 5th failed
+  EXPECT_FALSE(coil.setStages(stages));
+  EXPECT_EQ(4, coil.numberOfStages());
+  {
+    std::vector<CoilHeatingGasMultiStageStageData> thisStages = coil.stages();
+    for (unsigned i = 0; i < 4; ++i) {
+      EXPECT_EQ(stages[i], thisStages[i]);
+    }
+  }
+  stages.pop_back();
+  EXPECT_EQ(4u, stages.size());
+
+  {
+    const auto& stageAtEnd = stages.back();
+    auto optIndex = coil.stageIndex(stageAtEnd);
+    ASSERT_TRUE(optIndex);
+    EXPECT_EQ(coil.numberOfStages(), optIndex.get());
+
+    EXPECT_TRUE(coil.setStageIndex(stageAtEnd, 2));
+    std::vector<CoilHeatingGasMultiStageStageData> thisStages = coil.stages();
+    optIndex = coil.stageIndex(stageAtEnd);
+    ASSERT_TRUE(optIndex);
+    EXPECT_EQ(2, optIndex.get());
+    EXPECT_EQ(4, coil.numberOfStages());
+    for (unsigned i = 1; i <= coil.numberOfStages(); ++i) {
+      if (i < optIndex.get()) {
+        EXPECT_EQ(stages[i - 1], coil.stages()[i - 1]);
+      } else if (i > optIndex.get()) {
+        EXPECT_EQ(stages[i - 2], coil.stages()[i - 1]);
+      }
+    }
+  }
+
+  coil.removeAllStages();
+  EXPECT_EQ(0u, coil.numExtensibleGroups());
+  EXPECT_EQ(0u, coil.numberOfStages());
+  EXPECT_EQ(0u, coil.stages().size());
+
+  // Test that added a stage from another model will fail but not add a blank extensible group
+  Model model2;
+  CoilHeatingGasMultiStageStageData stageFromAnotherModel(model2);
+  EXPECT_FALSE(coil.addStage(stageFromAnotherModel));
+  EXPECT_EQ(0u, coil.numExtensibleGroups());
+  EXPECT_EQ(0u, coil.numberOfStages());
+  EXPECT_EQ(0u, coil.stages().size());
+}
+
+TEST_F(ModelFixture, CoilHeatingGasMultiStage_Remove) {
+  Model m;
+
+  CoilHeatingGasMultiStage coil(m);
+  CoilHeatingGasMultiStageStageData stage(m);
+  EXPECT_TRUE(coil.addStage(stage));
+  EXPECT_EQ(1, m.getConcreteModelObjects<CoilHeatingGasMultiStage>().size());
+  EXPECT_EQ(1, m.getConcreteModelObjects<CoilHeatingGasMultiStageStageData>().size());
+  coil.remove();
+
+  EXPECT_EQ(0, m.getConcreteModelObjects<CoilHeatingGasMultiStage>().size());
+  EXPECT_EQ(0, m.getConcreteModelObjects<CoilHeatingGasMultiStageStageData>().size());
+  auto schedules = m.getModelObjects<model::ScheduleConstant>();
+  auto limits = m.getModelObjects<model::ScheduleTypeLimits>();
+  EXPECT_EQ(schedules.size() + limits.size(), m.modelObjects().size());
+}
+
+TEST_F(ModelFixture, CoilHeatingGasMultiStage_Clone) {
+  // Ref #4663
+  Model m;
+  CoilHeatingGasMultiStage coil(m);
+
+  std::vector<CoilHeatingGasMultiStageStageData> stages{CoilHeatingGasMultiStageStageData{m}, CoilHeatingGasMultiStageStageData{m}};
+  EXPECT_TRUE(coil.setStages(stages));
+
+  EXPECT_EQ(2, coil.stages().size());
+  EXPECT_EQ(2, coil.numberOfStages());
+  EXPECT_EQ(1, m.getConcreteModelObjects<CoilHeatingGasMultiStage>().size());
+  EXPECT_EQ(2, m.getConcreteModelObjects<CoilHeatingGasMultiStageStageData>().size());
+
+  {
+    // Clone in another model first, so we don't affect the original one. Cloning also clones the stages
+    Model m2;
+    auto coilClone = coil.clone(m2).cast<CoilHeatingGasMultiStage>();
+    std::vector<CoilHeatingGasMultiStageStageData> stages2 = coilClone.stages();
+    EXPECT_EQ(2, stages2.size());
+    EXPECT_EQ(1, m2.getConcreteModelObjects<CoilHeatingGasMultiStage>().size());
+    EXPECT_EQ(2, m2.getConcreteModelObjects<CoilHeatingGasMultiStageStageData>().size());
+    for (auto& stage : stages) {
+      EXPECT_TRUE(std::find_if(stages2.cbegin(), stages2.cend(), [&stage](const auto& s) { return s.handle() == stage.handle(); }) == stages2.cend())
+        << "Did not expect to find, in the cloned Coil, the CoilHeatingGasMultiStageStageData '" << stage.nameString() << "'";
+    }
+
+    coilClone.remove();
+    EXPECT_EQ(0, m2.getConcreteModelObjects<CoilHeatingGasMultiStage>().size());
+    EXPECT_EQ(0, m2.getConcreteModelObjects<CoilHeatingGasMultiStageStageData>().size());
+  }
+
+  {
+    // Same model: we expect stages to be cloned and reassigned to the clone
+    auto coilClone = coil.clone(m).cast<CoilHeatingGasMultiStage>();
+    std::vector<CoilHeatingGasMultiStageStageData> stages2 = coilClone.stages();
+    EXPECT_EQ(2, stages2.size());
+    EXPECT_EQ(2, m.getConcreteModelObjects<CoilHeatingGasMultiStage>().size());
+    EXPECT_EQ(4, m.getConcreteModelObjects<CoilHeatingGasMultiStageStageData>().size());
+    for (auto& stage : stages) {
+      EXPECT_TRUE(std::find_if(stages2.cbegin(), stages2.cend(), [&stage](const auto& s) { return s.handle() == stage.handle(); }) == stages2.cend())
+        << "Did not expect to find, in the cloned Coil, the CoilHeatingGasMultiStageStageData '" << stage.nameString() << "'";
+    }
+    coil.remove();
+    EXPECT_EQ(1, m.getConcreteModelObjects<CoilHeatingGasMultiStage>().size());
+    EXPECT_EQ(2, m.getConcreteModelObjects<CoilHeatingGasMultiStageStageData>().size());
+
+    coilClone.remove();
+    EXPECT_EQ(0, m.getConcreteModelObjects<CoilHeatingGasMultiStage>().size());
+    EXPECT_EQ(0, m.getConcreteModelObjects<CoilHeatingGasMultiStageStageData>().size());
+  }
+}
diff --git a/src/osversion/test/3_5_0/test_vt_CoilHeatingGasMultiStage.osm b/src/osversion/test/3_5_0/test_vt_CoilHeatingGasMultiStage.osm
new file mode 100644
index 00000000000..b46ec59760b
--- /dev/null
+++ b/src/osversion/test/3_5_0/test_vt_CoilHeatingGasMultiStage.osm
@@ -0,0 +1,12 @@
+
+OS:Version,
+  {a442ec10-8a7c-41e0-9be2-301215acb95c}, !- Handle
+  3.4.0;                                  !- Version Identifier
+
+OS:Coil:Heating:Gas:MultiStage,
+  {076072a6-4433-4f42-8abd-ce4cb2b01811}, !- Handle
+  Coil Heating Gas Multi Stage 1,         !- Name
+  ,                                       !- Availability Schedule
+  ,                                       !- Air Inlet Node
+  ;                                       !- Air Outlet Node
+
diff --git a/src/osversion/test/3_5_0/test_vt_CoilHeatingGasMultiStage.rb b/src/osversion/test/3_5_0/test_vt_CoilHeatingGasMultiStage.rb
new file mode 100644
index 00000000000..da3f5e8aae9
--- /dev/null
+++ b/src/osversion/test/3_5_0/test_vt_CoilHeatingGasMultiStage.rb
@@ -0,0 +1,9 @@
+#require '/usr/local/openstudio-3.4.0/Ruby/openstudio'
+
+include OpenStudio::Model
+
+m = Model.new
+
+coil = CoilHeatingGasMultiStage.new(m)
+
+m.save('test_vt_CoilHeatingGasMultiStage.osm', true)
diff --git a/src/osversion/test/VersionTranslator_GTest.cpp b/src/osversion/test/VersionTranslator_GTest.cpp
index 885d2687e41..c6ac02cd03d 100644
--- a/src/osversion/test/VersionTranslator_GTest.cpp
+++ b/src/osversion/test/VersionTranslator_GTest.cpp
@@ -2205,3 +2205,20 @@ TEST_F(OSVersionFixture, update_3_4_0_to_3_5_0_SizingZone) {
   EXPECT_EQ(0.6, sz.getDouble(37).get());
   EXPECT_EQ(0.5, sz.getDouble(38).get());
 }
+
+TEST_F(OSVersionFixture, update_3_4_0_to_3_5_0_CoilHeatingGasMultiStage) {
+  openstudio::path path = resourcesPath() / toPath("osversion/3_5_0/test_vt_CoilHeatingGasMultiStage.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_0/test_vt_CoilHeatingGasMultiStage_updated.osm");
+  model->save(outPath, true);
+
+  std::vector<WorkspaceObject> coils = model->getObjectsByType("OS:Coil:Heating:Gas:MultiStage");
+  ASSERT_EQ(1u, coils.size());
+  WorkspaceObject coil = coils[0];
+
+  ASSERT_TRUE(coil.getTarget(2));
+  EXPECT_EQ("Always On Discrete", coil.getTarget(2)->nameString());
+}