Repository: NREL/OpenStudio (language: python)

Issue:
Strange crash when removing Unitheater from thermalzone equipment list
Issue overview
--------------

I am extremely puzzled about what's happening but I can reproduce a crash originally reported at https://github.com/openstudiocoalition/OpenStudioApplication/issues/686 on windows (it works on Ubuntu).

## Current Behavior

It seems that when the extensible group corresponding to the UH is deleted, something very strange happens.

In the following group, which points correctly to a ZoneVentilationDesignFlowRate object to begin with, the handle is changed to be the handle of that object's Schedule Name (?!)

## Expected Behavior
<!--- Tell us what should happen -->

## Steps to Reproduce

I'll provide a shortened file asap: edit: Here is a mcve.osm:

[mcve.osm.txt](https://github.com/NREL/OpenStudio/files/14742397/mcve.osm.txt)


```shell
C:\openstudio-3.7.0\bin\openstudio -e "m = OpenStudio::Model::Model::load('mcve.osm').get; uh = m.getZoneHVACUnitHeaters.first; uh.remove; puts 'ok'"
[BOOST_ASSERT] <2> Assertion eg failed on line 346 of bool __cdecl openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const class openstudio::model::ModelObject &) in file D:\OSN\src\model\ZoneHVACEquipmentList.cpp.
[BOOST_ASSERT] <2> Assertion eg failed on line 346 of bool __cdecl openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const class openstudio::model::ModelObject &) in file D:\OSN\src\model\ZoneHVACEquipmentList.cpp.
[BOOST_ASSERT] <2> Assertion eg failed on line 356 of bool __cdecl openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const class openstudio::model::ModelObject &) in file D:\OSN\src\model\ZoneHVACEquipmentList.cpp.
[BOOST_ASSERT] <2> Assertion eg failed on line 356 of bool __cdecl openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const class openstudio::model::ModelObject &) in file D:\OSN\src\model\ZoneHVACEquipmentList.cpp.
ok
```

```ruby
m = OpenStudio::Model::Model::load('mcve.osm')
uh.remove
```

test2:

```ruby
require 'openstudio'

include OpenStudio::Model

def getGroupForModelObject(eqlist, modelObject)
  eqlist.extensibleGroups.each do |eg|
    meg = eg.to_ModelExtensibleGroup.get
    if meg.getTarget(0).get.handle == modelObject.handle
      return meg
    end
  end
  return nil
end

m = OpenStudio::Model::Model::load('mcve.osm').get
eqlist = m.getZoneHVACEquipmentLists.first
uh = m.getZoneHVACUnitHeaters.first

puts "Eq List before"
pp eqlist.extensibleGroups.map{|eg| [eg.to_ModelExtensibleGroup.get.getTarget(0).get.nameString, eg.getUnsigned(1).get, eg.getUnsigned(2).get]}
puts eqlist

coolingVector = eqlist.equipmentInCoolingOrder
heatingVector = eqlist.equipmentInHeatingOrder

index = getGroupForModelObject(eqlist, uh).groupIndex
eqlist.eraseExtensibleGroup(index)
puts "Eq List After erasing extensible group #{index}"
pp eqlist.extensibleGroups.map{|eg| [eg.to_ModelExtensibleGroup.get.getTarget(0).get.nameString, eg.getUnsigned(1).get, eg.getUnsigned(2).get]}
puts eqlist
m.save('mcve_after_erase.osm', true)

heatingVector = heatingVector.reject{|x| x==uh}
coolingVector = coolingVector.reject{|x| x==uh}

priority = 1
coolingVector.each do |elem|
  eg = getGroupForModelObject(eqlist, elem)
  raise "Group is nil for #{elem.briefDescription}" if eg.nil?
  eg.setUnsigned(1, priority)
  priority += 1
end

priority = 1
heatingVector.each do |elem|
  eg = getGroupForModelObject(eqlist, elem)
  raise "Group is nil for #{elem.briefDescription}" if eg.nil?
  eg.setUnsigned(2, priority)
  priority += 1
end

puts "Eq List after"
pp eqlist.extensibleGroups.map{|eg| [eg.to_ModelExtensibleGroup.get.getTarget(0).get.nameString, eg.getUnsigned(1).get, eg.getUnsigned(2).get]}
```

On Linux, everything is fine. Comparing the mcve.osm and the mcve_after_erase.osm, I get what's expected:

![image](https://github.com/NREL/OpenStudio/assets/5479063/0862ca04-8997-4136-b72f-959a36f4f437)

Adding the WIndows one to the right, we see something weird happened, the handle for former second group and now first group is actually pointing to a Schedule

![image](https://github.com/NREL/OpenStudio/assets/5479063/aae42904-e492-442b-8629-78f3a3fc3450)

```
Eq List before
[["CV Unit Heater Gas", 1, 1],
 ["Natural Ventilation", 2, 2],
 ["Exhaust Ventilation 1", 3, 3]]
OS:ZoneHVAC:EquipmentList,
  {1fb73cb8-af9e-498c-835a-6ba8e9fc462d}, !- Handle
  Zone HVAC Equipment List 1,             !- Name
  {fc4c588a-3ee2-43bb-831f-3cb4edd4a60b}, !- Thermal Zone
  ,                                       !- Load Distribution Scheme
  {9ff16c2d-faa2-4235-8cd3-8c11ee784945}, !- Zone Equipment 1
  1,                                      !- Zone Equipment Cooling Sequence 1
  1,                                      !- Zone Equipment Heating or No-Load Sequence 1
  ,                                       !- Zone Equipment Sequential Cooling Fraction Schedule Name 1
  ,                                       !- Zone Equipment Sequential Heating Fraction Schedule Name 1
  {462cece8-1aa2-43b2-9c2b-b7988df19dd6}, !- Zone Equipment 2
  2,                                      !- Zone Equipment Cooling Sequence 2
  2,                                      !- Zone Equipment Heating or No-Load Sequence 2
  ,                                       !- Zone Equipment Sequential Cooling Fraction Schedule Name 2
  ,                                       !- Zone Equipment Sequential Heating Fraction Schedule Name 2
  {99ce599d-315f-4bd8-add2-726fcb42aaee}, !- Zone Equipment 3
  3,                                      !- Zone Equipment Cooling Sequence 3
  3,                                      !- Zone Equipment Heating or No-Load Sequence 3
  ,                                       !- Zone Equipment Sequential Cooling Fraction Schedule Name 3
  ;                                       !- Zone Equipment Sequential Heating Fraction Schedule Name 3

Eq List After erasing extensible group 0
[["Natural ventilation", 2, 2], ["Exhaust Ventilation 1", 3, 3]]
OS:ZoneHVAC:EquipmentList,
  {1fb73cb8-af9e-498c-835a-6ba8e9fc462d}, !- Handle
  Zone HVAC Equipment List 1,             !- Name
  {fc4c588a-3ee2-43bb-831f-3cb4edd4a60b}, !- Thermal Zone
  ,                                       !- Load Distribution Scheme
  {cbe1f37f-5b90-460b-9f75-c45094d6fb9e}, !- Zone Equipment 1
  2,                                      !- Zone Equipment Cooling Sequence 1
  2,                                      !- Zone Equipment Heating or No-Load Sequence 1
  ,                                       !- Zone Equipment Sequential Cooling Fraction Schedule Name 1
  ,                                       !- Zone Equipment Sequential Heating Fraction Schedule Name 1
  {99ce599d-315f-4bd8-add2-726fcb42aaee}, !- Zone Equipment 2
  3,                                      !- Zone Equipment Cooling Sequence 2
  3,                                      !- Zone Equipment Heating or No-Load Sequence 2
  ,                                       !- Zone Equipment Sequential Cooling Fraction Schedule Name 2
  ;                                       !- Zone Equipment Sequential Heating Fraction Schedule Name 2


Error: Group is nil for Object of type 'OS:ZoneVentilation:DesignFlowRate' and named 'Natural Ventilation'
Backtrace:
        C:/src/OpenStudioApplication-issues/os_app/686/test.rb:56:in `block in <top (required)>'
        C:/src/OpenStudioApplication-issues/os_app/686/test.rb:54:in `each'
        C:/src/OpenStudioApplication-issues/os_app/686/test.rb:54:in `<top (required)>'
        eval:139:in `require'
        eval:139:in `require'
        :/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:92:in `require'
        eval:4:in `<main>'
Failed to execute 'C:/src/OpenStudioApplication-issues/os_app/686/test.rb'
```


## Possible Solution


## Details

### Environment

Some additional details about your environment for this issue (if relevant):
 - Platform (Operating system, version): Windows
 - Version of OpenStudio (if using an intermediate build, include SHA): 3.7.0

### Context

https://github.com/openstudiocoalition/OpenStudioApplication/issues/686

Strange crash when removing Unitheater from thermalzone equipment list
Issue overview
--------------

I am extremely puzzled about what's happening but I can reproduce a crash originally reported at https://github.com/openstudiocoalition/OpenStudioApplication/issues/686 on windows (it works on Ubuntu).

## Current Behavior

It seems that when the extensible group corresponding to the UH is deleted, something very strange happens.

In the following group, which points correctly to a ZoneVentilationDesignFlowRate object to begin with, the handle is changed to be the handle of that object's Schedule Name (?!)

## Expected Behavior
<!--- Tell us what should happen -->

## Steps to Reproduce

I'll provide a shortened file asap: edit: Here is a mcve.osm:

[mcve.osm.txt](https://github.com/NREL/OpenStudio/files/14742397/mcve.osm.txt)


```shell
C:\openstudio-3.7.0\bin\openstudio -e "m = OpenStudio::Model::Model::load('mcve.osm').get; uh = m.getZoneHVACUnitHeaters.first; uh.remove; puts 'ok'"
[BOOST_ASSERT] <2> Assertion eg failed on line 346 of bool __cdecl openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const class openstudio::model::ModelObject &) in file D:\OSN\src\model\ZoneHVACEquipmentList.cpp.
[BOOST_ASSERT] <2> Assertion eg failed on line 346 of bool __cdecl openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const class openstudio::model::ModelObject &) in file D:\OSN\src\model\ZoneHVACEquipmentList.cpp.
[BOOST_ASSERT] <2> Assertion eg failed on line 356 of bool __cdecl openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const class openstudio::model::ModelObject &) in file D:\OSN\src\model\ZoneHVACEquipmentList.cpp.
[BOOST_ASSERT] <2> Assertion eg failed on line 356 of bool __cdecl openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const class openstudio::model::ModelObject &) in file D:\OSN\src\model\ZoneHVACEquipmentList.cpp.
ok
```

```ruby
m = OpenStudio::Model::Model::load('mcve.osm')
uh.remove
```

test2:

```ruby
require 'openstudio'

include OpenStudio::Model

def getGroupForModelObject(eqlist, modelObject)
  eqlist.extensibleGroups.each do |eg|
    meg = eg.to_ModelExtensibleGroup.get
    if meg.getTarget(0).get.handle == modelObject.handle
      return meg
    end
  end
  return nil
end

m = OpenStudio::Model::Model::load('mcve.osm').get
eqlist = m.getZoneHVACEquipmentLists.first
uh = m.getZoneHVACUnitHeaters.first

puts "Eq List before"
pp eqlist.extensibleGroups.map{|eg| [eg.to_ModelExtensibleGroup.get.getTarget(0).get.nameString, eg.getUnsigned(1).get, eg.getUnsigned(2).get]}
puts eqlist

coolingVector = eqlist.equipmentInCoolingOrder
heatingVector = eqlist.equipmentInHeatingOrder

index = getGroupForModelObject(eqlist, uh).groupIndex
eqlist.eraseExtensibleGroup(index)
puts "Eq List After erasing extensible group #{index}"
pp eqlist.extensibleGroups.map{|eg| [eg.to_ModelExtensibleGroup.get.getTarget(0).get.nameString, eg.getUnsigned(1).get, eg.getUnsigned(2).get]}
puts eqlist
m.save('mcve_after_erase.osm', true)

heatingVector = heatingVector.reject{|x| x==uh}
coolingVector = coolingVector.reject{|x| x==uh}

priority = 1
coolingVector.each do |elem|
  eg = getGroupForModelObject(eqlist, elem)
  raise "Group is nil for #{elem.briefDescription}" if eg.nil?
  eg.setUnsigned(1, priority)
  priority += 1
end

priority = 1
heatingVector.each do |elem|
  eg = getGroupForModelObject(eqlist, elem)
  raise "Group is nil for #{elem.briefDescription}" if eg.nil?
  eg.setUnsigned(2, priority)
  priority += 1
end

puts "Eq List after"
pp eqlist.extensibleGroups.map{|eg| [eg.to_ModelExtensibleGroup.get.getTarget(0).get.nameString, eg.getUnsigned(1).get, eg.getUnsigned(2).get]}
```

On Linux, everything is fine. Comparing the mcve.osm and the mcve_after_erase.osm, I get what's expected:

![image](https://github.com/NREL/OpenStudio/assets/5479063/0862ca04-8997-4136-b72f-959a36f4f437)

Adding the WIndows one to the right, we see something weird happened, the handle for former second group and now first group is actually pointing to a Schedule

![image](https://github.com/NREL/OpenStudio/assets/5479063/aae42904-e492-442b-8629-78f3a3fc3450)

```
Eq List before
[["CV Unit Heater Gas", 1, 1],
 ["Natural Ventilation", 2, 2],
 ["Exhaust Ventilation 1", 3, 3]]
OS:ZoneHVAC:EquipmentList,
  {1fb73cb8-af9e-498c-835a-6ba8e9fc462d}, !- Handle
  Zone HVAC Equipment List 1,             !- Name
  {fc4c588a-3ee2-43bb-831f-3cb4edd4a60b}, !- Thermal Zone
  ,                                       !- Load Distribution Scheme
  {9ff16c2d-faa2-4235-8cd3-8c11ee784945}, !- Zone Equipment 1
  1,                                      !- Zone Equipment Cooling Sequence 1
  1,                                      !- Zone Equipment Heating or No-Load Sequence 1
  ,                                       !- Zone Equipment Sequential Cooling Fraction Schedule Name 1
  ,                                       !- Zone Equipment Sequential Heating Fraction Schedule Name 1
  {462cece8-1aa2-43b2-9c2b-b7988df19dd6}, !- Zone Equipment 2
  2,                                      !- Zone Equipment Cooling Sequence 2
  2,                                      !- Zone Equipment Heating or No-Load Sequence 2
  ,                                       !- Zone Equipment Sequential Cooling Fraction Schedule Name 2
  ,                                       !- Zone Equipment Sequential Heating Fraction Schedule Name 2
  {99ce599d-315f-4bd8-add2-726fcb42aaee}, !- Zone Equipment 3
  3,                                      !- Zone Equipment Cooling Sequence 3
  3,                                      !- Zone Equipment Heating or No-Load Sequence 3
  ,                                       !- Zone Equipment Sequential Cooling Fraction Schedule Name 3
  ;                                       !- Zone Equipment Sequential Heating Fraction Schedule Name 3

Eq List After erasing extensible group 0
[["Natural ventilation", 2, 2], ["Exhaust Ventilation 1", 3, 3]]
OS:ZoneHVAC:EquipmentList,
  {1fb73cb8-af9e-498c-835a-6ba8e9fc462d}, !- Handle
  Zone HVAC Equipment List 1,             !- Name
  {fc4c588a-3ee2-43bb-831f-3cb4edd4a60b}, !- Thermal Zone
  ,                                       !- Load Distribution Scheme
  {cbe1f37f-5b90-460b-9f75-c45094d6fb9e}, !- Zone Equipment 1
  2,                                      !- Zone Equipment Cooling Sequence 1
  2,                                      !- Zone Equipment Heating or No-Load Sequence 1
  ,                                       !- Zone Equipment Sequential Cooling Fraction Schedule Name 1
  ,                                       !- Zone Equipment Sequential Heating Fraction Schedule Name 1
  {99ce599d-315f-4bd8-add2-726fcb42aaee}, !- Zone Equipment 2
  3,                                      !- Zone Equipment Cooling Sequence 2
  3,                                      !- Zone Equipment Heating or No-Load Sequence 2
  ,                                       !- Zone Equipment Sequential Cooling Fraction Schedule Name 2
  ;                                       !- Zone Equipment Sequential Heating Fraction Schedule Name 2


Error: Group is nil for Object of type 'OS:ZoneVentilation:DesignFlowRate' and named 'Natural Ventilation'
Backtrace:
        C:/src/OpenStudioApplication-issues/os_app/686/test.rb:56:in `block in <top (required)>'
        C:/src/OpenStudioApplication-issues/os_app/686/test.rb:54:in `each'
        C:/src/OpenStudioApplication-issues/os_app/686/test.rb:54:in `<top (required)>'
        eval:139:in `require'
        eval:139:in `require'
        :/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:92:in `require'
        eval:4:in `<main>'
Failed to execute 'C:/src/OpenStudioApplication-issues/os_app/686/test.rb'
```


## Possible Solution


## Details

### Environment

Some additional details about your environment for this issue (if relevant):
 - Platform (Operating system, version): Windows
 - Version of OpenStudio (if using an intermediate build, include SHA): 3.7.0

### Context

https://github.com/openstudiocoalition/OpenStudioApplication/issues/686



Discussion / hints:
Oh well well well. The schedule is named exactly (casing aside) like the ZoneVentilationDesignFlowRate object... I found that's the precondition that makes me also reproduce on linux.

**test_scratch.rb**:

```ruby
require 'openstudio'

include OpenStudio::Model

m = Model.new

z = ThermalZone.new(m)

uh = ZoneHVACUnitHeater.new(m, m.alwaysOnDiscreteSchedule, FanOnOff.new(m), CoilHeatingElectric.new(m))
nv = ZoneVentilationDesignFlowRate.new(m)
nv.setName("ZVDFR NaturalVentilation")
nv.setVentilationType("Natural")
nv_sch = ScheduleRuleset.new(m)
nv.setSchedule(nv_sch)
nv_sch.setName(nv.nameString)      # <-------- comment out this line and it works fine

exhaust = ZoneVentilationDesignFlowRate.new(m)
exhaust.setVentilationType("Exhaust")
exhaust_sch = ScheduleRuleset.new(m)
exhaust.setSchedule(exhaust_sch)
#exhaust_sch.setName(exhaust.nameString)

uh.addToThermalZone(z)
nv.addToThermalZone(z)
exhaust.addToThermalZone(z)

uh.remove
```

On ubuntu:

```shell
$ ruby test_scratch.rb 
[BOOST_ASSERT] <2> Assertion eg failed on line 346 of bool openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const openstudio::model::ModelObject&) in file /srv/jenkins/openstudio/git/nightly/ubuntu_2004/src/model/ZoneHVACEquipmentList.cpp.
[BOOST_ASSERT] <2> Assertion eg failed on line 346 of bool openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const openstudio::model::ModelObject&) in file /srv/jenkins/openstudio/git/nightly/ubuntu_2004/src/model/ZoneHVACEquipmentList.cpp.
[BOOST_ASSERT] <2> Assertion eg failed on line 356 of bool openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const openstudio::model::ModelObject&) in file /srv/jenkins/openstudio/git/nightly/ubuntu_2004/src/model/ZoneHVACEquipmentList.cpp.
[BOOST_ASSERT] <2> Assertion eg failed on line 356 of bool openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const openstudio::model::ModelObject&) in file /srv/jenkins/openstudio/git/nightly/ubuntu_2004/src/model/ZoneHVACEquipmentList.cpp.
```
As long as grabbing by name returns the schedule first, I can reproduce the crash

```ruby
m = Model.new

z = ThermalZone.new(m)

bb_delete = ZoneHVACBaseboardConvectiveElectric.new(m)

bb = ZoneHVACBaseboardConvectiveElectric.new(m)
bb_sch = ScheduleConstant.new(m)
bb.setName("Baseboard")
bb_sch.setName(bb.nameString)
bb.setAvailabilitySchedule(bb_sch)

bb_delete.addToThermalZone(z)
bb.addToThermalZone(z)

puts "Objects named #{bb.nameString}"
puts m.objects.select{|o| o.nameString == bb.nameString}.map(&:iddObject).map(&:type)
raise unless m.getObjectsByName(bb.nameString).first.iddObject.type == "OS_Schedule_Constant".to_IddObjectType

bb_delete.remove
```
Oh well well well. The schedule is named exactly (casing aside) like the ZoneVentilationDesignFlowRate object... I found that's the precondition that makes me also reproduce on linux.

**test_scratch.rb**:

```ruby
require 'openstudio'

include OpenStudio::Model

m = Model.new

z = ThermalZone.new(m)

uh = ZoneHVACUnitHeater.new(m, m.alwaysOnDiscreteSchedule, FanOnOff.new(m), CoilHeatingElectric.new(m))
nv = ZoneVentilationDesignFlowRate.new(m)
nv.setName("ZVDFR NaturalVentilation")
nv.setVentilationType("Natural")
nv_sch = ScheduleRuleset.new(m)
nv.setSchedule(nv_sch)
nv_sch.setName(nv.nameString)      # <-------- comment out this line and it works fine

exhaust = ZoneVentilationDesignFlowRate.new(m)
exhaust.setVentilationType("Exhaust")
exhaust_sch = ScheduleRuleset.new(m)
exhaust.setSchedule(exhaust_sch)
#exhaust_sch.setName(exhaust.nameString)

uh.addToThermalZone(z)
nv.addToThermalZone(z)
exhaust.addToThermalZone(z)

uh.remove
```

On ubuntu:

```shell
$ ruby test_scratch.rb 
[BOOST_ASSERT] <2> Assertion eg failed on line 346 of bool openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const openstudio::model::ModelObject&) in file /srv/jenkins/openstudio/git/nightly/ubuntu_2004/src/model/ZoneHVACEquipmentList.cpp.
[BOOST_ASSERT] <2> Assertion eg failed on line 346 of bool openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const openstudio::model::ModelObject&) in file /srv/jenkins/openstudio/git/nightly/ubuntu_2004/src/model/ZoneHVACEquipmentList.cpp.
[BOOST_ASSERT] <2> Assertion eg failed on line 356 of bool openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const openstudio::model::ModelObject&) in file /srv/jenkins/openstudio/git/nightly/ubuntu_2004/src/model/ZoneHVACEquipmentList.cpp.
[BOOST_ASSERT] <2> Assertion eg failed on line 356 of bool openstudio::model::detail::ZoneHVACEquipmentList_Impl::removeEquipment(const openstudio::model::ModelObject&) in file /srv/jenkins/openstudio/git/nightly/ubuntu_2004/src/model/ZoneHVACEquipmentList.cpp.
```
As long as grabbing by name returns the schedule first, I can reproduce the crash

```ruby
m = Model.new

z = ThermalZone.new(m)

bb_delete = ZoneHVACBaseboardConvectiveElectric.new(m)

bb = ZoneHVACBaseboardConvectiveElectric.new(m)
bb_sch = ScheduleConstant.new(m)
bb.setName("Baseboard")
bb_sch.setName(bb.nameString)
bb.setAvailabilitySchedule(bb_sch)

bb_delete.addToThermalZone(z)
bb.addToThermalZone(z)

puts "Objects named #{bb.nameString}"
puts m.objects.select{|o| o.nameString == bb.nameString}.map(&:iddObject).map(&:type)
raise unless m.getObjectsByName(bb.nameString).first.iddObject.type == "OS_Schedule_Constant".to_IddObjectType

bb_delete.remove
```

Fix patch:
diff --git a/src/utilities/idf/IdfExtensibleGroup.cpp b/src/utilities/idf/IdfExtensibleGroup.cpp
index 03a2bd65815..614099264f1 100644
--- a/src/utilities/idf/IdfExtensibleGroup.cpp
+++ b/src/utilities/idf/IdfExtensibleGroup.cpp
@@ -22,6 +22,17 @@ StringVector IdfExtensibleGroup::fields(bool returnDefault) const {
   return result;
 }
 
+StringVector IdfExtensibleGroup::fieldsWithHandles(bool returnDefault) const {
+  StringVector result;
+  const unsigned n = numFields();
+  result.reserve(n);
+  for (unsigned i = 0; i < n; ++i) {
+    OptionalString str = getField(i, returnDefault);
+    result.push_back(str.get_value_or(""));
+  }
+  return result;
+}
+
 StringVector IdfExtensibleGroup::fieldComments(bool returnDefault) const {
   StringVector result;
   for (unsigned i = 0, n = numFields(); i < n; ++i) {
@@ -57,6 +68,13 @@ OptionalString IdfExtensibleGroup::getString(unsigned fieldIndex, bool returnDef
   return m_impl->getString(mf_toIndex(fieldIndex), returnDefault);
 }
 
+boost::optional<std::string> IdfExtensibleGroup::getField(unsigned index, bool returnDefault) const {
+  if (!isValid(index)) {
+    return boost::none;
+  }
+  return m_impl->getField(mf_toIndex(index), returnDefault);
+}
+
 OptionalDouble IdfExtensibleGroup::getDouble(unsigned fieldIndex, bool returnDefault) const {
   if (!isValid(fieldIndex)) {
     return boost::none;
@@ -169,7 +187,7 @@ IdfExtensibleGroup IdfExtensibleGroup::pushClone() const {
     return {p, 0};
   }
 
-  StringVector values = fields();
+  StringVector values = fieldsWithHandles();
   OS_ASSERT(values.size() == numFields());
   return m_impl->pushExtensibleGroup(values);
 }
@@ -180,7 +198,7 @@ IdfExtensibleGroup IdfExtensibleGroup::insertClone(unsigned groupIndex) const {
     return {p, 0};
   }
 
-  StringVector values = fields();
+  StringVector values = fieldsWithHandles();
   OS_ASSERT(values.size() == numFields());
   return m_impl->insertExtensibleGroup(groupIndex, values);
 }
diff --git a/src/utilities/idf/IdfExtensibleGroup.hpp b/src/utilities/idf/IdfExtensibleGroup.hpp
index 15a2fef9364..c391a3a699b 100644
--- a/src/utilities/idf/IdfExtensibleGroup.hpp
+++ b/src/utilities/idf/IdfExtensibleGroup.hpp
@@ -40,10 +40,13 @@ class UTILITIES_API IdfExtensibleGroup
   /** @name Getters */
   //@{
 
-  /** Returns this extensible group's fields. Return value will be empty() if this group is
-   *  empty(). */
+  /** Returns this extensible group's fields. Return value will be empty() if this group is empty(). */
   std::vector<std::string> fields(bool returnDefault = false) const;
 
+  /** Returns this extensible group's fields. Return value will be empty() if this group is empty().
+    * Unlike fields(), in the case it's a WorkspaceObject_Impl, it uses handles as string and not the name of the target object */
+  std::vector<std::string> fieldsWithHandles(bool returnDefault = false) const;
+
   /** Returns the comments associated with this extensible group's fields. */
   std::vector<std::string> fieldComments(bool returnDefault = false) const;
 
@@ -54,6 +57,10 @@ class UTILITIES_API IdfExtensibleGroup
    *  exists (isValid(fieldIndex)). */
   boost::optional<std::string> getString(unsigned fieldIndex, bool returnDefault = false) const;
 
+  /** Like getString except for reference fields getString will return the name of the referenced object.
+   * This method, getField, will always return the string value  of the field.   */
+  boost::optional<std::string> getField(unsigned index, bool returnDefault = false) const;
+
   /** Returns true if the field is empty. */
   bool isEmpty(unsigned fieldIndex) const;
 
diff --git a/src/utilities/idf/IdfObject.cpp b/src/utilities/idf/IdfObject.cpp
index 6ed0a47475b..4356d407245 100644
--- a/src/utilities/idf/IdfObject.cpp
+++ b/src/utilities/idf/IdfObject.cpp
@@ -209,6 +209,10 @@ namespace detail {
     return boost::none;
   }
 
+  boost::optional<std::string> IdfObject_Impl::getField(unsigned index, bool returnDefault) const {
+    return IdfObject_Impl::getString(index, returnDefault, false);
+  }
+
   boost::optional<double> IdfObject_Impl::getDouble(unsigned index, bool returnDefault) const {
     OptionalDouble result;
     OptionalString value = getString(index, returnDefault, false);
@@ -687,7 +691,7 @@ namespace detail {
       unsigned i = numExtensibleGroups() - 1;
       IdfExtensibleGroup eg = getExtensibleGroup(i);
       OS_ASSERT(!eg.empty());
-      IdfExtensibleGroup temp = pushExtensibleGroup(eg.fields(), checkValidity);
+      IdfExtensibleGroup temp = pushExtensibleGroup(eg.fieldsWithHandles(), checkValidity);
       if (temp.empty()) {
         OS_ASSERT(numFields() == n);
         OS_ASSERT(m_diffs.size() == diffSize);
@@ -701,7 +705,7 @@ namespace detail {
         --i;
         IdfExtensibleGroup peg = getExtensibleGroup(i);
         OS_ASSERT(!peg.empty());
-        ok = eg.setFields(peg.fields(), false);
+        ok = eg.setFields(peg.fieldsWithHandles(), false);
         if (!ok) {
           // roll back
           i += 2;
@@ -710,7 +714,7 @@ namespace detail {
             OS_ASSERT(!peg.empty());
             eg = getExtensibleGroup(i + 1);
             OS_ASSERT(!eg.empty());
-            peg.setFields(eg.fields(), false);
+            peg.setFields(eg.fieldsWithHandles(), false);
             ++i;
           }
           popExtensibleGroup(false);
@@ -734,7 +738,7 @@ namespace detail {
           ++i;
           eg = getExtensibleGroup(i);
           OS_ASSERT(!eg.empty());
-          peg.setFields(eg.fields(), false);
+          peg.setFields(eg.fieldsWithHandles(), false);
           peg = eg;
         }
         popExtensibleGroup(false);
@@ -774,7 +778,7 @@ namespace detail {
       IdfExtensibleGroup egToPop = getExtensibleGroup(numExtensibleGroups() - 1);
       OS_ASSERT(!egToPop.empty());
       UnsignedVector indices = egToPop.mf_indices();
-      result = egToPop.fields();
+      result = egToPop.fieldsWithHandles();
       OS_ASSERT(result.size() == groupSize);
 
       // record diffs for each field going backwards
@@ -825,14 +829,14 @@ namespace detail {
       StringVector temp = result;
       IdfExtensibleGroup eg = getExtensibleGroup(i);
       OS_ASSERT(!eg.empty());
-      result = eg.fields();
+      result = eg.fieldsWithHandles();
       ok = eg.setFields(temp, checkValidity);
       if (!ok) {
         // roll back changes and return
         for (unsigned j = i + 1, n = numExtensibleGroups(); j < n; ++j) {
           eg = getExtensibleGroup(j);
           OS_ASSERT(!eg.empty());
-          result = eg.fields();
+          result = eg.fieldsWithHandles();
           OS_ASSERT(result.size() == temp.size());
           eg.setFields(temp, false);
           temp = result;
@@ -884,7 +888,7 @@ namespace detail {
       if (indices.empty()) {
         indices = eg.mf_indices();
       }
-      rollbackValues.push_back(eg.fields());
+      rollbackValues.push_back(eg.fieldsWithHandles());
       rollbackComments.push_back(eg.fieldComments());
       // try to pop
       StringVector result = popExtensibleGroup(checkValidity);
@@ -2063,6 +2067,10 @@ boost::optional<std::string> IdfObject::getString(unsigned index, bool returnDef
   return m_impl->getString(index, returnDefault, returnUninitializedEmpty);
 }
 
+boost::optional<std::string> IdfObject::getField(unsigned index, bool returnDefault) const {
+  return m_impl->getField(index, returnDefault);
+}
+
 boost::optional<double> IdfObject::getDouble(unsigned index, bool returnDefault) const {
   return m_impl->getDouble(index, returnDefault);
 }
diff --git a/src/utilities/idf/IdfObject.hpp b/src/utilities/idf/IdfObject.hpp
index 312c8d92226..484f96e933b 100644
--- a/src/utilities/idf/IdfObject.hpp
+++ b/src/utilities/idf/IdfObject.hpp
@@ -144,6 +144,11 @@ class UTILITIES_API IdfObject
    */
   boost::optional<std::string> getString(unsigned index, bool returnDefault = false, bool returnUninitializedEmpty = false) const;
 
+  /** Like getString except for reference fields getString will return the name of the referenced object.
+    * This method, getField, will always return the string value of the field.
+    * For IdfObject, this is the same as getString */
+  boost::optional<std::string> getField(unsigned index, bool returnDefault = false) const;
+
   /** Get the value of the field at index, converted to double, if possible. Returns an
    *  uninitialized object if the conversion is unsuccessful for any reason. Logs a warning
    *  if the conversion fails, the field is RealType, and the field is not equal to
diff --git a/src/utilities/idf/IdfObject_Impl.hpp b/src/utilities/idf/IdfObject_Impl.hpp
index cabeafa86d9..8e011d29923 100644
--- a/src/utilities/idf/IdfObject_Impl.hpp
+++ b/src/utilities/idf/IdfObject_Impl.hpp
@@ -101,6 +101,11 @@ namespace detail {
      */
     virtual boost::optional<std::string> getString(unsigned index, bool returnDefault = false, bool returnUninitializedEmpty = false) const;
 
+    /** Like getString except for reference fields getString will return the name of the referenced object.
+     * This method, getField, will always return the string value of the field.
+     * For IdfObject, this is the same as getString */
+    virtual boost::optional<std::string> getField(unsigned index, bool returnDefault = false) const;
+
     /** Get the value of the field at index, converted to double, if possible. Returns an
      *  uninitialized object if the conversion is unsuccessful for any reason. Logs a warning
      *  if the conversion fails, the field is RealType, and the field is not equal to
diff --git a/src/utilities/idf/Test/ExtensibleGroup_GTest.cpp b/src/utilities/idf/Test/ExtensibleGroup_GTest.cpp
index 590119a37c9..c3d136437bb 100644
--- a/src/utilities/idf/Test/ExtensibleGroup_GTest.cpp
+++ b/src/utilities/idf/Test/ExtensibleGroup_GTest.cpp
@@ -469,11 +469,14 @@ TEST_F(IdfFixture, ExtensibleGroup_Erase) {
   objects[3].setName("Gypsum");
   WorkspaceObjectVector temp = ws.addObjects(objects);
   ASSERT_EQ(static_cast<unsigned>(4), temp.size());
-  ASSERT_TRUE(temp[0].iddObject().type() == IddObjectType::Construction);
-  ASSERT_TRUE(temp[1].name().get() == "Brick");
-  ASSERT_TRUE(temp[2].name().get() == "Insulation");
-  ASSERT_TRUE(temp[3].name().get() == "Gypsum");
-  WorkspaceObject wsConstruction = temp[0];
+  WorkspaceObject& wsConstruction = temp[0];
+  WorkspaceObject& wsBrick = temp[1];
+  WorkspaceObject& wsInsulation = temp[2];
+  WorkspaceObject& wsGypsum = temp[3];
+  ASSERT_TRUE(wsConstruction.iddObject().type() == IddObjectType::Construction);
+  ASSERT_TRUE(wsBrick.name().get() == "Brick");
+  ASSERT_TRUE(wsInsulation.name().get() == "Insulation");
+  ASSERT_TRUE(wsGypsum.name().get() == "Gypsum");
 
   values[0] = "Brick";
   eg = wsConstruction.pushExtensibleGroup(values);
@@ -488,17 +491,19 @@ TEST_F(IdfFixture, ExtensibleGroup_Erase) {
   ASSERT_FALSE(eg.empty());  // Brick, Insulation, Insulation, Gypsum
 
   // erase and check pointers
-  EXPECT_EQ(static_cast<unsigned>(2), temp[2].numSources());
+  EXPECT_EQ(static_cast<unsigned>(2), wsInsulation.numSources());
   values = wsConstruction.eraseExtensibleGroup(1);
   ASSERT_EQ(static_cast<unsigned>(1), values.size());
-  EXPECT_EQ("Insulation", values[0]);  // Brick, Insulation, Gypsum
-  EXPECT_EQ(static_cast<unsigned>(1), temp[2].numSources());
+  EXPECT_EQ(openstudio::toString(wsInsulation.handle()), values[0]);  // Brick, Insulation, Gypsum
+  EXPECT_EQ(static_cast<unsigned>(1), wsInsulation.numSources());
+
   values = wsConstruction.eraseExtensibleGroup(2);
   ASSERT_EQ(static_cast<unsigned>(1), values.size());
-  EXPECT_EQ("Gypsum", values[0]);  // Brick, Insulation
-  EXPECT_EQ(static_cast<unsigned>(1), temp[1].numSources());
-  EXPECT_EQ(static_cast<unsigned>(1), temp[2].numSources());
-  EXPECT_EQ(static_cast<unsigned>(0), temp[3].numSources());
+  EXPECT_EQ(openstudio::toString(wsGypsum.handle()), values[0]);  // Brick, Insulation
+
+  EXPECT_EQ(static_cast<unsigned>(1), wsBrick.numSources());
+  EXPECT_EQ(static_cast<unsigned>(1), wsInsulation.numSources());
+  EXPECT_EQ(static_cast<unsigned>(0), wsGypsum.numSources());
   ASSERT_EQ(static_cast<unsigned>(3), wsConstruction.numFields());
   EXPECT_EQ("Brick", wsConstruction.getString(1).get());
   EXPECT_EQ("Insulation", wsConstruction.getString(2).get());
diff --git a/src/utilities/idf/Test/WorkspaceObject_GTest.cpp b/src/utilities/idf/Test/WorkspaceObject_GTest.cpp
index 555a329554e..e0fcfff4275 100644
--- a/src/utilities/idf/Test/WorkspaceObject_GTest.cpp
+++ b/src/utilities/idf/Test/WorkspaceObject_GTest.cpp
@@ -10,8 +10,13 @@
 #include <utilities/idd/Zone_FieldEnums.hxx>
 #include <utilities/idd/Lights_FieldEnums.hxx>
 #include <utilities/idd/Schedule_Compact_FieldEnums.hxx>
+
 #include <utilities/idd/OS_DaylightingDevice_Shelf_FieldEnums.hxx>
 #include <utilities/idd/OS_SetpointManager_MixedAir_FieldEnums.hxx>
+#include <utilities/idd/OS_ZoneHVAC_EquipmentList_FieldEnums.hxx>
+#include <utilities/idd/OS_ZoneHVAC_Baseboard_Convective_Electric_FieldEnums.hxx>
+#include <utilities/idd/OS_Schedule_Constant_FieldEnums.hxx>
+
 #include <utilities/idd/IddFactory.hxx>
 
 #include "../WorkspaceExtensibleGroup.hpp"
@@ -19,6 +24,7 @@
 #include "../IdfObject.hpp"
 #include "../Workspace.hpp"
 #include "../WorkspaceObject.hpp"
+#include "../WorkspaceObject_Impl.hpp"
 
 #include "../../core/Optional.hpp"
 
@@ -585,6 +591,59 @@ TEST_F(IdfFixture, WorkspaceObject_setName_allObjects) {
   }
 }
 
+TEST_F(IdfFixture, WorkspaceObject_eraseExtensibleGroups) {
+
+  Workspace w(StrictnessLevel::Draft, IddFileType::OpenStudio);
+  WorkspaceObject eqlist = w.addObject(IdfObject(IddObjectType::OS_ZoneHVAC_EquipmentList)).get();
+
+  WorkspaceObject bb_delete = w.addObject(IdfObject(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric)).get();
+  {
+    auto weg = eqlist.pushExtensibleGroup().cast<WorkspaceExtensibleGroup>();
+    EXPECT_TRUE(weg.setPointer(0, bb_delete.handle()));
+    EXPECT_TRUE(weg.setUnsigned(1, 1));
+    EXPECT_TRUE(weg.setUnsigned(2, 1));
+  }
+
+  WorkspaceObject bb = w.addObject(IdfObject(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric)).get();
+
+  WorkspaceObject bb_sch = w.addObject(IdfObject(IddObjectType::OS_Schedule_Constant)).get();
+  EXPECT_TRUE(bb.setPointer(2, bb_sch.handle()));
+
+  {
+    auto weg = eqlist.pushExtensibleGroup().cast<WorkspaceExtensibleGroup>();
+    EXPECT_TRUE(weg.setPointer(0, bb.handle()));
+    EXPECT_TRUE(weg.setUnsigned(1, 1));
+    EXPECT_TRUE(weg.setUnsigned(2, 1));
+  }
+
+  EXPECT_TRUE(bb.setName("Baseboard"));
+  EXPECT_TRUE(bb_sch.setName(bb.nameString()));
+
+  EXPECT_EQ(bb.nameString(), bb_sch.nameString());
+
+  auto objects = w.getObjectsByName(bb.nameString());
+  EXPECT_EQ(2, objects.size());
+  EXPECT_EQ(IddObjectType(IddObjectType::OS_Schedule_Constant), objects.front().iddObject().type());
+
+  EXPECT_EQ(2, eqlist.numExtensibleGroups());
+  for (const auto& eg : eqlist.extensibleGroups()) {
+    const auto weg = eg.cast<WorkspaceExtensibleGroup>();
+    auto target_ = weg.getTarget(OS_ZoneHVAC_EquipmentListExtensibleFields::ZoneEquipment);
+    ASSERT_TRUE(target_);
+    EXPECT_EQ(IddObjectType(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric), target_->iddObject().type());
+  }
+
+  eqlist.eraseExtensibleGroup(0);
+
+  EXPECT_EQ(1, eqlist.numExtensibleGroups());
+  for (const auto& eg : eqlist.extensibleGroups()) {
+    const auto weg = eg.cast<WorkspaceExtensibleGroup>();
+    auto target_ = weg.getTarget(OS_ZoneHVAC_EquipmentListExtensibleFields::ZoneEquipment);
+    ASSERT_TRUE(target_);
+    EXPECT_EQ(IddObjectType(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric), target_->iddObject().type());
+  }
+}
+
 TEST_F(IdfFixture, WorkspaceObject_SpecialMembers) {
 
   static_assert(!std::is_trivial<WorkspaceObject>{});
diff --git a/src/utilities/idf/WorkspaceExtensibleGroup.cpp b/src/utilities/idf/WorkspaceExtensibleGroup.cpp
index ab3de5dcfbe..7d7ac687da8 100644
--- a/src/utilities/idf/WorkspaceExtensibleGroup.cpp
+++ b/src/utilities/idf/WorkspaceExtensibleGroup.cpp
@@ -45,13 +45,6 @@ std::vector<unsigned> WorkspaceExtensibleGroup::getSourceFieldIndices(const Hand
   return result;
 }
 
-boost::optional<std::string> WorkspaceExtensibleGroup::getField(unsigned index) const {
-  if (!isValid(index)) {
-    return boost::none;
-  }
-  return getImpl<detail::WorkspaceObject_Impl>()->getField(mf_toIndex(index));
-}
-
 // SETTERS
 bool WorkspaceExtensibleGroup::setPointer(unsigned fieldIndex, const Handle& targetHandle) {
   return setPointer(fieldIndex, targetHandle, true);
diff --git a/src/utilities/idf/WorkspaceExtensibleGroup.hpp b/src/utilities/idf/WorkspaceExtensibleGroup.hpp
index 1180dbc9613..08b0af76ca1 100644
--- a/src/utilities/idf/WorkspaceExtensibleGroup.hpp
+++ b/src/utilities/idf/WorkspaceExtensibleGroup.hpp
@@ -42,12 +42,6 @@ class UTILITIES_API WorkspaceExtensibleGroup : public IdfExtensibleGroup
    *  if possible. */
   std::vector<unsigned> getSourceFieldIndices(const Handle& targetHandle) const;
 
-  /** Like getString except for reference fields getString will return the
-   *  name of the referenced object. This method, getField, will always return the string value
-   *  of the field.
-   */
-  boost::optional<std::string> getField(unsigned index) const;
-
   //@}
   /** @name Setters */
   //@{
diff --git a/src/utilities/idf/WorkspaceObject.cpp b/src/utilities/idf/WorkspaceObject.cpp
index f0680444902..1c004b76cb1 100644
--- a/src/utilities/idf/WorkspaceObject.cpp
+++ b/src/utilities/idf/WorkspaceObject.cpp
@@ -188,7 +188,7 @@ namespace detail {
     return IdfObject_Impl::getString(index, returnDefault, returnUninitializedEmpty);
   }
 
-  boost::optional<std::string> WorkspaceObject_Impl::getField(unsigned index) const {
+  boost::optional<std::string> WorkspaceObject_Impl::getField(unsigned index, bool returnDefault) const {
     boost::optional<std::string> result;
 
     // IdfObject_Impl::getString won't work if this is a source field... since m_fields will be blank
@@ -212,7 +212,7 @@ namespace detail {
       }
     } else {
       // If not source, then call getString
-      result = IdfObject_Impl::getString(index, false, false);
+      result = IdfObject_Impl::getString(index, returnDefault, false);
     }
 
     return result;
@@ -1376,10 +1376,6 @@ bool WorkspaceObject::objectListFieldsNonConflicting(const WorkspaceObject& othe
   return getImpl<WorkspaceObject_Impl>()->objectListFieldsNonConflicting(other);
 }
 
-boost::optional<std::string> WorkspaceObject::getField(unsigned index) const {
-  return getImpl<WorkspaceObject_Impl>()->getField(index);
-}
-
 // SERIALIZATION
 
 IdfObject WorkspaceObject::idfObject() {
diff --git a/src/utilities/idf/WorkspaceObject.hpp b/src/utilities/idf/WorkspaceObject.hpp
index 5694ac9b36e..2e19971d3f8 100644
--- a/src/utilities/idf/WorkspaceObject.hpp
+++ b/src/utilities/idf/WorkspaceObject.hpp
@@ -66,12 +66,6 @@ class UTILITIES_API WorkspaceObject : public IdfObject
   /** Returns all objects of type that point to this object, filtering for duplicate objects. */
   std::vector<WorkspaceObject> getSources(IddObjectType type) const;
 
-  /** Like getString except for reference fields getString will return the
-   *  name of the referenced object. This method, getField, will always return the string value
-   *  of the field.
-   */
-  boost::optional<std::string> getField(unsigned index) const;
-
   //@}
   /** @name Setters */
   //@{
diff --git a/src/utilities/idf/WorkspaceObject_Impl.hpp b/src/utilities/idf/WorkspaceObject_Impl.hpp
index 62a2451abe0..c0aebcc9f5e 100644
--- a/src/utilities/idf/WorkspaceObject_Impl.hpp
+++ b/src/utilities/idf/WorkspaceObject_Impl.hpp
@@ -129,7 +129,7 @@ namespace detail {
      *  (non-extensible) fields and fields with empty data, if a default exists. */
     virtual boost::optional<std::string> getString(unsigned index, bool returnDefault = false, bool returnUninitializedEmpty = false) const override;
 
-    boost::optional<std::string> getField(unsigned index) const;
+    virtual boost::optional<std::string> getField(unsigned index, bool returnDefault = false) const override;
 
     /** Returns the object pointed to by the field at index, if it exists. */
     boost::optional<WorkspaceObject> getTarget(unsigned index) const;


Test patch:
diff --git a/src/model/test/ZoneHVACEquipmentList_GTest.cpp b/src/model/test/ZoneHVACEquipmentList_GTest.cpp
index 635e3d04b63..6b8c1ac15fd 100644
--- a/src/model/test/ZoneHVACEquipmentList_GTest.cpp
+++ b/src/model/test/ZoneHVACEquipmentList_GTest.cpp
@@ -3,17 +3,28 @@
 *  See also https://openstudio.net/license
 ***********************************************************************************************************************/
 
-#include <algorithm>
-
 #include <gtest/gtest.h>
 
 #include "ModelFixture.hpp"
+
+#include "../ZoneHVACEquipmentList.hpp"
+#include "../ZoneHVACEquipmentList_Impl.hpp"
+
 #include "../ThermalZone.hpp"
 #include "../ZoneHVACBaseboardConvectiveElectric.hpp"
 #include "../ScheduleConstant.hpp"
 #include "../Schedule.hpp"
 #include "../ScheduleTypeLimits.hpp"
 
+#include "../../utilities/idd/IddObject.hpp"
+#include "../../utilities/idf/IdfObject.hpp"
+#include "../../utilities/idf/IdfExtensibleGroup.hpp"
+
+#include <utilities/idd/OS_ZoneHVAC_EquipmentList_FieldEnums.hxx>
+#include <utilities/idd/IddEnums.hxx>
+
+#include <algorithm>
+
 using namespace openstudio;
 using namespace openstudio::model;
 
@@ -143,3 +154,216 @@ TEST_F(ModelFixture, ZoneHVACEquipmentList_ScheduleTypeLimits) {
   ASSERT_TRUE(_sch_lim);
   EXPECT_EQ("Dimensionless", _sch_lim->unitType());
 }
+
+TEST_F(ModelFixture, ZoneHVACEquipmentList_RemoveEquipment_ModelObject_Is_First) {
+
+  Model m;
+  ThermalZone z(m);
+
+  auto eqlists = m.getConcreteModelObjects<ZoneHVACEquipmentList>();
+  EXPECT_EQ(1, eqlists.size());
+  auto& eqlist = eqlists.front();
+
+  ZoneHVACBaseboardConvectiveElectric bb_delete(m);
+
+  ScheduleConstant bb_sch(m);
+
+  ZoneHVACBaseboardConvectiveElectric bb(m);
+
+  bb.setName("Baseboard");
+  bb_sch.setName(bb.nameString());
+  EXPECT_TRUE(bb.setAvailabilitySchedule(bb_sch));
+  EXPECT_EQ(bb.nameString(), bb_sch.nameString());
+
+  EXPECT_TRUE(bb_delete.addToThermalZone(z));
+  EXPECT_TRUE(bb.addToThermalZone(z));
+
+  {
+    EXPECT_EQ(2, eqlist.numExtensibleGroups());
+    auto idf_egs = eqlist.extensibleGroups();
+    for (const auto& idf_eg : idf_egs) {
+      // Using getField so we don't resolve object name, but get the handle string
+      const auto val_ = idf_eg.getField(OS_ZoneHVAC_EquipmentListExtensibleFields::ZoneEquipment);
+      const auto uid = openstudio::toUUID(*val_);
+      auto obj_ = m.getObject(uid);
+      ASSERT_TRUE(obj_);
+      EXPECT_EQ(IddObjectType(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric), obj_->iddObject().type());
+    }
+  }
+
+  {
+    auto objects = m.getObjectsByName(bb.nameString());
+    EXPECT_EQ(2, objects.size());
+    EXPECT_EQ(IddObjectType(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric), objects.front().iddObject().type());
+  }
+
+  EXPECT_NO_THROW(bb_delete.remove());
+
+  {
+    EXPECT_EQ(1, eqlist.numExtensibleGroups());
+    auto idf_egs = eqlist.extensibleGroups();
+    for (const auto& idf_eg : idf_egs) {
+      // Using getField so we don't resolve object name, but get the handle string
+      const auto val_ = idf_eg.getField(OS_ZoneHVAC_EquipmentListExtensibleFields::ZoneEquipment);
+      const auto uid = openstudio::toUUID(*val_);
+      auto obj_ = m.getObject(uid);
+      ASSERT_TRUE(obj_);
+      EXPECT_EQ(IddObjectType(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric), obj_->iddObject().type());
+    }
+  }
+}
+
+TEST_F(ModelFixture, ZoneHVACEquipmentList_RemoveEquipment_Schedule_Is_First) {
+
+  Model m;
+  ThermalZone z(m);
+
+  auto eqlists = m.getConcreteModelObjects<ZoneHVACEquipmentList>();
+  EXPECT_EQ(1, eqlists.size());
+  auto& eqlist = eqlists.front();
+
+  ZoneHVACBaseboardConvectiveElectric bb_delete(m);
+
+  ZoneHVACBaseboardConvectiveElectric bb(m);
+
+  ScheduleConstant bb_sch(m);
+
+  bb.setName("Baseboard");
+  bb_sch.setName(bb.nameString());
+  EXPECT_TRUE(bb.setAvailabilitySchedule(bb_sch));
+  EXPECT_EQ(bb.nameString(), bb_sch.nameString());
+
+  EXPECT_TRUE(bb_delete.addToThermalZone(z));
+  EXPECT_TRUE(bb.addToThermalZone(z));
+
+  {
+    EXPECT_EQ(2, eqlist.numExtensibleGroups());
+    auto idf_egs = eqlist.extensibleGroups();
+    for (const auto& idf_eg : idf_egs) {
+      // Using getField so we don't resolve object name, but get the handle string
+      const auto val_ = idf_eg.getField(OS_ZoneHVAC_EquipmentListExtensibleFields::ZoneEquipment);
+      const auto uid = openstudio::toUUID(*val_);
+      auto obj_ = m.getObject(uid);
+      ASSERT_TRUE(obj_);
+      EXPECT_EQ(IddObjectType(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric), obj_->iddObject().type());
+    }
+  }
+
+  {
+    auto objects = m.getObjectsByName(bb.nameString());
+    EXPECT_EQ(2, objects.size());
+    EXPECT_EQ(IddObjectType(IddObjectType::OS_Schedule_Constant), objects.front().iddObject().type());
+  }
+
+  EXPECT_NO_THROW(bb_delete.remove());
+
+  {
+    EXPECT_EQ(1, eqlist.numExtensibleGroups());
+    auto idf_egs = eqlist.extensibleGroups();
+    for (const auto& idf_eg : idf_egs) {
+      // Using getField so we don't resolve object name, but get the handle string
+      const auto val_ = idf_eg.getField(OS_ZoneHVAC_EquipmentListExtensibleFields::ZoneEquipment);
+      const auto uid = openstudio::toUUID(*val_);
+      auto obj_ = m.getObject(uid);
+      ASSERT_TRUE(obj_);
+      EXPECT_EQ(IddObjectType(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric), obj_->iddObject().type());
+    }
+  }
+}
+
+TEST_F(ModelFixture, ZoneHVACEquipmentList_RemoveEquipment_Schedule_Is_First_OnlyPop) {
+
+  Model m;
+  ThermalZone z(m);
+
+  auto eqlists = m.getConcreteModelObjects<ZoneHVACEquipmentList>();
+  EXPECT_EQ(1, eqlists.size());
+  auto& eqlist = eqlists.front();
+
+  ZoneHVACBaseboardConvectiveElectric bb_delete(m);
+
+  ZoneHVACBaseboardConvectiveElectric bb(m);
+
+  ScheduleConstant bb_sch(m);
+
+  bb.setName("Baseboard");
+  bb_sch.setName(bb.nameString());
+  EXPECT_TRUE(bb.setAvailabilitySchedule(bb_sch));
+  EXPECT_EQ(bb.nameString(), bb_sch.nameString());
+
+  EXPECT_TRUE(bb_delete.addToThermalZone(z));
+  EXPECT_TRUE(bb.addToThermalZone(z));
+
+  EXPECT_EQ(2, eqlist.numExtensibleGroups());
+  // I deliberately use idfObject.extensibleGroups so the handles aren't resolved to object name
+  auto idf_egs = eqlist.idfObject().extensibleGroups();
+
+  for (const auto& idf_eg : idf_egs) {
+    const std::string val = idf_eg.getString(OS_ZoneHVAC_EquipmentListExtensibleFields::ZoneEquipment).get();
+    const auto uid = openstudio::toUUID(val);
+    auto obj_ = m.getObject(uid);
+    ASSERT_TRUE(obj_);
+    EXPECT_EQ(IddObjectType(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric), obj_->iddObject().type());
+  }
+
+  eqlist.eraseExtensibleGroup(0);
+
+  EXPECT_EQ(1, eqlist.numExtensibleGroups());
+  idf_egs = eqlist.idfObject().extensibleGroups();
+  for (const auto& idf_eg : idf_egs) {
+    const std::string val = idf_eg.getString(OS_ZoneHVAC_EquipmentListExtensibleFields::ZoneEquipment).get();
+    const auto uid = openstudio::toUUID(val);
+    auto obj_ = m.getObject(uid);
+    ASSERT_TRUE(obj_);
+    EXPECT_EQ(IddObjectType(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric), obj_->iddObject().type());
+  }
+}
+
+TEST_F(ModelFixture, ZoneHVACEquipmentList_RemoveEquipment_Schedule_OnlyPop_Works_With_IdfObject) {
+
+  Model m;
+  ThermalZone z(m);
+
+  auto eqlists = m.getConcreteModelObjects<ZoneHVACEquipmentList>();
+  EXPECT_EQ(1, eqlists.size());
+  auto& eqlist = eqlists.front();
+
+  ZoneHVACBaseboardConvectiveElectric bb_delete(m);
+
+  ZoneHVACBaseboardConvectiveElectric bb(m);
+
+  ScheduleConstant bb_sch(m);
+
+  bb.setName("Baseboard");
+  bb_sch.setName(bb.nameString());
+  EXPECT_TRUE(bb.setAvailabilitySchedule(bb_sch));
+  EXPECT_EQ(bb.nameString(), bb_sch.nameString());
+
+  EXPECT_TRUE(bb_delete.addToThermalZone(z));
+  EXPECT_TRUE(bb.addToThermalZone(z));
+
+  EXPECT_EQ(2, eqlist.numExtensibleGroups());
+  // I deliberately use idfObject.extensibleGroups so the handles aren't resolved to object name
+  auto idf_egs = eqlist.idfObject().extensibleGroups();
+
+  for (const auto& idf_eg : idf_egs) {
+    const std::string val = idf_eg.getString(OS_ZoneHVAC_EquipmentListExtensibleFields::ZoneEquipment).get();
+    const auto uid = openstudio::toUUID(val);
+    auto obj_ = m.getObject(uid);
+    ASSERT_TRUE(obj_);
+    EXPECT_EQ(IddObjectType(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric), obj_->iddObject().type());
+  }
+
+  auto i = eqlist.idfObject();
+  i.eraseExtensibleGroup(0);
+
+  EXPECT_EQ(1, i.numExtensibleGroups());
+  idf_egs = i.extensibleGroups();
+  for (const auto& idf_eg : idf_egs) {
+    const std::string val = idf_eg.getString(OS_ZoneHVAC_EquipmentListExtensibleFields::ZoneEquipment).get();
+    const auto uid = openstudio::toUUID(val);
+    auto obj_ = m.getObject(uid);
+    ASSERT_TRUE(obj_);
+    EXPECT_EQ(IddObjectType(IddObjectType::OS_ZoneHVAC_Baseboard_Convective_Electric), obj_->iddObject().type());
+  }
+}