Repository: NREL/OpenStudio (language: python)

Issue:
When removing an AirLoopHVAC:DedicatedOutdoorAirSystem, also remove the associated OutdoorAirSystem
Enhancement Request
--------------
Reimplement `remove` for AirLoopHVAC:DedicatedOutdoorAirSystem to also remove the associated AirLoopHVAC:OutdoorAirSystem

## Detailed Description
Currently, if I call `remove` on an AirLoopHVAC:DedicatedOutdoorAirSystem, only the object is removed, but the associated OutdoorAirSystem (and any coils/fans/setpointmanagers on that system) remain in the model: 

```
m = Model.new
# build the DOAS
oas = AirLoopHVACOutdoorAirSystem.new(m, ControllerOutdoorAir.new(m))
doas = AirLoopHVACDedicatedOutdoorAirSystem.new(oas)
h = CoilHeatingElectric.new(m)
f = FanSystemModel.new(m)
f.addToNode(oas.outboardOANode.get)
h.addToNode(oas.outboardOANode.get)
spm = SetpointManagerOutdoorAirReset.new(m)
spm.addToNode(h.outletModelObject.get.to_Node.get)

# remove the DOAS
m.getAirLoopHVACDedicatedOutdoorAirSystems.each(&:remove)
puts m
```

Most of what's left is not forward translated, _except the SetpointManager_, which will cause EnergyPlus to fail because the referenced setpoint node doesn't exist:

```
ft = OpenStudio::EnergyPlus::ForwardTranslator.new()
ws = ft.translateModel(m)
puts ws
=>
...other stuff...
SetpointManager:OutdoorAirReset,
  Setpoint Manager Outdoor Air Reset 1,   !- Name
  Temperature,                            !- Control Variable
  22,                                     !- Setpoint at Outdoor Low Temperature {C}
  10,                                     !- Outdoor Low Temperature {C}
  10,                                     !- Setpoint at Outdoor High Temperature {C}
  24,                                     !- Outdoor High Temperature {C}
  Node 4;                                 !- Setpoint Node or NodeList Name
...other stuff...
```

Since the AirLoopHVAC:OutdoorAirSystem only exists for the DOAS, it should be removed with the DOAS.

## Possible Implementation




Discussion / hints:

Fix patch:
diff --git a/src/energyplus/Test/AirLoopHVACDedicatedOutdoorAirSystem_GTest.cpp b/src/energyplus/Test/AirLoopHVACDedicatedOutdoorAirSystem_GTest.cpp
index 2340458bc76..d340ad4e96a 100644
--- a/src/energyplus/Test/AirLoopHVACDedicatedOutdoorAirSystem_GTest.cpp
+++ b/src/energyplus/Test/AirLoopHVACDedicatedOutdoorAirSystem_GTest.cpp
@@ -54,8 +54,12 @@
 #include "../../model/CoilCoolingWater_Impl.hpp"
 #include "../../model/CoilCoolingDXTwoSpeed.hpp"
 #include "../../model/CoilCoolingDXTwoSpeed_Impl.hpp"
+#include "../../model/CoilHeatingElectric.hpp"
+#include "../../model/CoilHeatingElectric_Impl.hpp"
 #include "../../model/HeatExchangerAirToAirSensibleAndLatent.hpp"
 #include "../../model/HeatExchangerAirToAirSensibleAndLatent_Impl.hpp"
+#include "../../model/SetpointManagerOutdoorAirReset.hpp"
+#include "../../model/SetpointManagerOutdoorAirReset_Impl.hpp"
 
 #include "../../utilities/idf/IdfObject.hpp"
 #include "../../utilities/idf/WorkspaceObject.hpp"
@@ -79,7 +83,6 @@ TEST_F(EnergyPlusFixture, ForwardTranslator_AirLoopHVACDedicatedOutdoorAirSystem
 
   AirLoopHVAC airLoop(m);
   airLoop.setName("Air Loop 1");
-
   ControllerOutdoorAir controller1(m);
   AirLoopHVACOutdoorAirSystem oaSystem1(m, controller1);
   oaSystem1.setName("Outdoor Air System 1");
@@ -220,6 +223,49 @@ TEST_F(EnergyPlusFixture, ForwardTranslator_AirLoopHVACDedicatedOutdoorAirSystem
   }
 }
 
+TEST_F(EnergyPlusFixture, ForwardTranslator_AirLoopHVACDedicatedOutdoorAirSystem_DeleteOAS) {
+  // Test for #4539 - Delete OutdoorAirSystem when deleting AirLoopHVAC:DedicatedOutdoorAirSystem
+
+  Model m;
+
+  AirLoopHVAC airLoop(m);
+  airLoop.setName("Air Loop 1");
+  ControllerOutdoorAir controller1(m);
+  AirLoopHVACOutdoorAirSystem oaSystem1(m, controller1);
+  oaSystem1.setName("Outdoor Air System 1");
+  CoilHeatingElectric coil1(m);
+  coil1.addToNode(oaSystem1.outboardOANode().get());
+
+  ControllerOutdoorAir controller2(m);
+  AirLoopHVACOutdoorAirSystem oaSystem2(m, controller2);
+  oaSystem2.setName("Outdoor Air System 2");
+  CoilHeatingElectric coil2(m);
+  coil2.addToNode(oaSystem2.outboardOANode().get());
+
+  AirLoopHVACDedicatedOutdoorAirSystem doaSystem(oaSystem1);
+  doaSystem.setName("Dedicated Outdoor Air System 1");
+  doaSystem.addAirLoop(airLoop);
+
+  SetpointManagerOutdoorAirReset spm1(m);
+  SetpointManagerOutdoorAirReset spm2(m);
+  spm1.addToNode(coil1.outletModelObject()->optionalCast<Node>().get());
+  spm1.addToNode(coil2.outletModelObject()->optionalCast<Node>().get());
+
+  // delete doaSystem and make sure oaSystem2 is deleted (oaSystem1 remains)
+  doaSystem.remove();
+
+  ForwardTranslator ft;
+  Workspace w = ft.translateModel(m);
+
+  // assert there is only one setpoint manager left in the model
+  WorkspaceObjectVector idfSTPTs(w.getObjectsByType(IddObjectType::SetpointManager_OutdoorAirReset));
+  ASSERT_EQ(1u, idfSTPTs.size());
+
+  // assert there are no DOAS left
+  WorkspaceObjectVector idfDOASs(w.getObjectsByType(IddObjectType::AirLoopHVAC_DedicatedOutdoorAirSystem));
+  ASSERT_EQ(0u, idfDOASs.size());
+}
+
 TEST_F(EnergyPlusFixture, ForwardTranslator_AirLoopHVACDedicatedOutdoorAirSystem_CoilCoolingDXTwoSpeed) {
 
   // Test for #4290 - Add a StraightComponent to an AirLoopHVAC:OutdoorAirSystem attached to AirLoopHVAC:DedicatedOutdoorAirSystem
diff --git a/src/model/AirLoopHVACDedicatedOutdoorAirSystem.cpp b/src/model/AirLoopHVACDedicatedOutdoorAirSystem.cpp
index fb8ae13745e..0c37f613b65 100644
--- a/src/model/AirLoopHVACDedicatedOutdoorAirSystem.cpp
+++ b/src/model/AirLoopHVACDedicatedOutdoorAirSystem.cpp
@@ -273,6 +273,13 @@ namespace model {
       clearExtensibleGroups();
     }
 
+    std::vector<openstudio::IdfObject> AirLoopHVACDedicatedOutdoorAirSystem_Impl::remove() {
+
+      airLoopHVACOutdoorAirSystem().remove();
+
+      return ModelObject_Impl::remove();
+    }
+
     bool AirLoopHVACDedicatedOutdoorAirSystem_Impl::addAirLoops(const std::vector<AirLoopHVAC>& airLoopHVACs) {
       bool ok = true;
       for (const AirLoopHVAC& airLoopHVAC : airLoopHVACs) {
diff --git a/src/model/AirLoopHVACDedicatedOutdoorAirSystem_Impl.hpp b/src/model/AirLoopHVACDedicatedOutdoorAirSystem_Impl.hpp
index 5aee5395471..1c5448ee1bd 100644
--- a/src/model/AirLoopHVACDedicatedOutdoorAirSystem_Impl.hpp
+++ b/src/model/AirLoopHVACDedicatedOutdoorAirSystem_Impl.hpp
@@ -115,6 +115,8 @@ namespace model {
 
       void removeAllAirLoops();
 
+      virtual std::vector<openstudio::IdfObject> remove() override;
+
       bool addAirLoops(const std::vector<AirLoopHVAC>& airLoopHVACs);
 
       //@}


Test patch:
diff --git a/src/model/test/AirLoopHVACDedicatedOutdoorAirSystem_GTest.cpp b/src/model/test/AirLoopHVACDedicatedOutdoorAirSystem_GTest.cpp
index 0b5c3875e4e..22b04642047 100644
--- a/src/model/test/AirLoopHVACDedicatedOutdoorAirSystem_GTest.cpp
+++ b/src/model/test/AirLoopHVACDedicatedOutdoorAirSystem_GTest.cpp
@@ -40,6 +40,7 @@
 #include "../ScheduleConstant.hpp"
 #include "../ScheduleConstant_Impl.hpp"
 #include "../Node.hpp"
+#include "../Node_Impl.hpp"
 #include "../CoilCoolingDXSingleSpeed.hpp"
 #include "../CoilCoolingDXTwoSpeed.hpp"
 #include "../CoilCoolingDXTwoStageWithHumidityControlMode.hpp"
@@ -62,6 +63,8 @@
 #include "../HumidifierSteamElectric.hpp"
 #include "../HumidifierSteamGas.hpp"
 #include "../HeatExchangerAirToAirSensibleAndLatent.hpp"
+#include "../SetpointManagerOutdoorAirReset.hpp"
+#include "../SetpointManagerOutdoorAirReset_Impl.hpp"
 
 #include "../../utilities/idd/IddEnums.hpp"
 #include <utilities/idd/IddEnums.hxx>
@@ -272,6 +275,40 @@ TEST_F(ModelFixture, AirLoopHVACDedicatedOutdoorAirSystem_Remove2) {
   EXPECT_FALSE(oaSystem.airLoopHVACDedicatedOutdoorAirSystem());
 }
 
+TEST_F(ModelFixture, AirLoopHVACDedicatedOutdoorAirSystem_Remove3) {
+  Model model;
+  std::vector<AirLoopHVACDedicatedOutdoorAirSystem> doaSystems = model.getModelObjects<AirLoopHVACDedicatedOutdoorAirSystem>();
+  EXPECT_EQ(0u, doaSystems.size());
+  std::vector<SetpointManagerOutdoorAirReset> spms = model.getModelObjects<SetpointManagerOutdoorAirReset>();
+
+  ControllerOutdoorAir controller(model);
+  AirLoopHVACOutdoorAirSystem oaSystem(model, controller);
+  AirLoopHVAC airLoop(model);
+  AirLoopHVAC airLoop2(model);
+  std::vector<AirLoopHVAC> airLoopHVACs;
+  airLoopHVACs.push_back(airLoop);
+  airLoopHVACs.push_back(airLoop2);
+
+  AirLoopHVACDedicatedOutdoorAirSystem doaSystem(oaSystem);
+  doaSystem.addAirLoops(airLoopHVACs);
+
+  CoilHeatingElectric coil(model);
+  coil.addToNode(oaSystem.outboardOANode().get());
+
+  SetpointManagerOutdoorAirReset spm(model);
+  spm.addToNode(coil.outletModelObject()->optionalCast<Node>().get());
+
+  doaSystems = model.getModelObjects<AirLoopHVACDedicatedOutdoorAirSystem>();
+  EXPECT_EQ(1u, doaSystems.size());
+
+  // make sure there are no traces of the outdoorAirSystem leftover after DOAS is removed
+  EXPECT_FALSE(doaSystem.remove().empty());
+  doaSystems = model.getModelObjects<AirLoopHVACDedicatedOutdoorAirSystem>();
+  EXPECT_EQ(0u, doaSystems.size());
+  spms = model.getModelObjects<SetpointManagerOutdoorAirReset>();
+  EXPECT_EQ(0u, spms.size());
+}
+
 TEST_F(ModelFixture, AirLoopHVACDedicatedOutdoorAirSystem_SupportedComponents) {
   Model m;
   ControllerOutdoorAir controller(m);