Skip to content

Conversation

@jmarrec
Copy link
Collaborator

@jmarrec jmarrec commented May 5, 2025

Pull request overview

Pull Request Author

  • Model API Changes / Additions
  • Any new or modified fields have been implemented in the EnergyPlus ForwardTranslator (and ReverseTranslator as appropriate)
  • Model API methods are tested (in src/model/test)
  • EnergyPlus ForwardTranslator Tests (in src/energyplus/Test)
  • If a new object or method, added a test in NREL/OpenStudio-resources: Add Link
  • If needed, added VersionTranslation rules for the objects (src/osversion/VersionTranslator.cpp)
  • Verified that C# bindings built fine on Windows, partial classes used as needed, etc.
  • All new and existing tests passes
  • If methods have been deprecated, update rest of code to use the new methods

Labels:

  • If change to an IDD file, add the label IDDChange
  • If breaking existing API, add the label APIChange
  • If deemed ready, add label Pull Request - Ready for CI so that CI builds your PR

Review Checklist

This will not be exhaustively relevant to every PR.

  • Perform a Code Review on GitHub
  • Code Style, strip trailing whitespace, etc.
  • All related changes have been implemented: model changes, model tests, FT changes, FT tests, VersionTranslation, OS App
  • Labeling is ok
  • If defect, verify by running develop branch and reproducing defect, then running PR and reproducing fix
  • If feature, test running new feature, try creative ways to break it
  • CI status: all green or justified

jmarrec added 10 commits May 5, 2025 11:44
…companion to CoilCoolingWater per IDD/IO ref

CoilSystem:Cooling:Water:HeatExchangerAssisted isn't  WaterToAirComponent
… of the primary cooling cooling of a CoilSystemCoolingWater to an AirLoopHVAC

Ensures coils that are part of a cooling system are not directly connected to an air loop, preventing potential errors.

Also manages the creation and removal of ControllerWaterCoil objects based on the coil's parent system and presence within a ZoneHVACComponent.
Schedule availabilitySchedule() const;

WaterToAirComponent coolingCoil() const;
HVACComponent coolingCoil() const;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • CoilCoolingWater > WaterToAirComponent > HVACComponent
  • CoilSystemCoolingWaterHeatExchangerAssisted > StraightComponent > HVACComponent

double minimumWaterLoopTemperatureForHeatRecovery() const;

boost::optional<WaterToAirComponent> companionCoilUsedForHeatRecovery() const;
boost::optional<HVACComponent> companionCoilUsedForHeatRecovery() const;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically the return type could stay WaterToAirComponent because the only supported type "AT THE MOMENT" is CoilCoolingWater. And I don't want to have to break API later on, so I also changed it.

Comment on lines +54 to 55
// This function will connect the air side only, for water side, use the coils directly
virtual bool addToNode(Node& node) override;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment was misleading.

Comment on lines +57 to +58
// Need to disconnect the Water side of the coil(s) before removing
virtual std::vector<IdfObject> remove() override;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Override remove.

Comment on lines +55 to 56
// This function will connect the air side only, for water side, use the coils directly
virtual bool addToNode(Node& node) override;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment was misleading (copied)

Comment on lines +140 to +165
TEST_F(ModelFixture, CoilSystemCoolingWater_ExplicitCtor) {
{
Model m;
CoilCoolingWater coolingCoil(m);
CoilSystemCoolingWater coilSystem(m, coolingCoil);
EXPECT_EQ(coolingCoil, coilSystem.coolingCoil());
EXPECT_EQ(1, m.getConcreteModelObjects<CoilSystemCoolingWater>().size());
EXPECT_EQ(1, m.getConcreteModelObjects<CoilCoolingWater>().size());
}

{
Model m;
CoilSystemCoolingWaterHeatExchangerAssisted coolingCoil(m);
CoilSystemCoolingWater coilSystem(m, coolingCoil);
EXPECT_EQ(coolingCoil, coilSystem.coolingCoil());
EXPECT_EQ(1, m.getConcreteModelObjects<CoilSystemCoolingWater>().size());
EXPECT_EQ(1, m.getConcreteModelObjects<CoilSystemCoolingWaterHeatExchangerAssisted>().size());
}

{
Model m;
CoilCoolingDXSingleSpeed coolingCoil(m);
EXPECT_ANY_THROW(CoilSystemCoolingWater coilSystem(m, coolingCoil));
EXPECT_EQ(0, m.getConcreteModelObjects<CoilSystemCoolingWater>().size());
EXPECT_EQ(1, m.getConcreteModelObjects<CoilCoolingDXSingleSpeed>().size());
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test the explicit ctor, which highlighted that we weren't handling the last case where you pass a wrogn coil type.

Comment on lines 154 to -139
boost::optional<HVACComponent> CoilSystemCoolingWater_Impl::containingHVACComponent() const {
// AirLoopHVACUnitarySystem
std::vector<AirLoopHVACUnitarySystem> airLoopHVACUnitarySystems = this->model().getConcreteModelObjects<AirLoopHVACUnitarySystem>();

for (const auto& airLoopHVACUnitarySystem : airLoopHVACUnitarySystems) {
if (boost::optional<HVACComponent> coolingCoil = airLoopHVACUnitarySystem.coolingCoil()) {
if (coolingCoil->handle() == this->handle()) {
return airLoopHVACUnitarySystem;
}
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CANNOT be part of a containing HVACComponent AFAIK

Comment on lines 158 to -149
boost::optional<ZoneHVACComponent> CoilSystemCoolingWater_Impl::containingZoneHVACComponent() const {

// ZoneHVACFourPipeFanCoil
std::vector<ZoneHVACFourPipeFanCoil> zoneHVACFourPipeFanCoils;

zoneHVACFourPipeFanCoils = this->model().getConcreteModelObjects<ZoneHVACFourPipeFanCoil>();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CANNOT be part of a containing ZoneHVACComponent AFAIK

This is from the E+ IDD, there are ZERO possible references.

Comment on lines 330 to 335
bool ok = true;
ok = setCoolingCoil(coolingCoil);
if (!ok) {
remove();
LOG_AND_THROW("Unable to set " << briefDescription() << "'s Cooling Coil " << coolingCoil.briefDescription() << ".");
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how you handle the explicit ctor being passed the wrong object, which can definitely happen.

Comment on lines +189 to +333
TEST_F(ModelFixture, CoilSystemCoolingWater_clone_remove) {

TEST_F(ModelFixture, CoilSystemCoolingWater_remove) {}
Model m;

// Wrap Around Water Coil Heat Recovery Mode
// CoilSystem has:
// - Primary Cooling Coil: a CoilSystemCoolingWaterHeatExchangerAssisted
// - This HXAssisted has a CoilCoolingWater as its coolingCoil
// - Companion Coil: a CoilCoolingWater
//
// CoilSystem is connected to an AirLoopHVACOutdoorAirSystem on the OA Intake
// Companion Coil is connected to OA Relief
// Both coils are connected to a PlantLoop in series on the demand side

CoilSystemCoolingWaterHeatExchangerAssisted coilSystemHXAssisted(m);
auto coolingCoil = coilSystemHXAssisted.coolingCoil().cast<CoilCoolingWater>();
coolingCoil.setName("Primary Cooling Coil inside HXAssisted");
CoilSystemCoolingWater coilSystem(m, coilSystemHXAssisted);

CoilCoolingWater companionCoil(m);
companionCoil.setName("Companion Coil");
EXPECT_TRUE(coilSystem.setCompanionCoilUsedForHeatRecovery(companionCoil));

// Air side connections
AirLoopHVAC a(m);
AirLoopHVACOutdoorAirSystem oa_sys(m);
EXPECT_EQ(2, a.supplyComponents().size()); // o --- o
EXPECT_EQ(1, oa_sys.oaComponents().size()); // o
EXPECT_EQ(1, oa_sys.reliefComponents().size()); // o
{
Node n = a.supplyOutletNode();
EXPECT_TRUE(oa_sys.addToNode(n));
}
EXPECT_EQ(3, a.supplyComponents().size()); // o --- oa_sys --- o
EXPECT_EQ(1, oa_sys.oaComponents().size()); // o
EXPECT_EQ(1, oa_sys.reliefComponents().size()); // o
{
Node n = oa_sys.outboardOANode().get();
EXPECT_TRUE(coilSystem.addToNode(n));
}
EXPECT_EQ(3, a.supplyComponents().size()); // o --- oa_sys --- o
EXPECT_EQ(3, oa_sys.oaComponents().size()); // o --- coilSystem --- o
EXPECT_EQ(1, oa_sys.reliefComponents().size()); // o
{
Node n = oa_sys.outboardReliefNode().get();
EXPECT_TRUE(companionCoil.addToNode(n));
}
EXPECT_EQ(3, a.supplyComponents().size()); // o --- oa_sys --- o
EXPECT_EQ(3, oa_sys.oaComponents().size()); // o --- coilSystem --- o
EXPECT_EQ(3, oa_sys.reliefComponents().size()); // o --- companionCoil --- o

// Plant Side connections: in series
PlantLoop p(m);
EXPECT_EQ(5, p.demandComponents().size()); // o --- Splitter --- o --- Mixer --- o

EXPECT_TRUE(p.addDemandBranchForComponent(coolingCoil));
EXPECT_EQ(7, p.demandComponents().size()); // o --- Splitter --- o --- coolingCoil --- Mixer --- o

{
Node n = coolingCoil.waterOutletModelObject()->cast<Node>();
EXPECT_TRUE(companionCoil.addToNode(n));
}
EXPECT_EQ(9, p.demandComponents().size()); // o --- Splitter --- o --- coolingCoil --- o --- HR Coil --- o --- Mixer --- o

EXPECT_EQ(1, m.getConcreteModelObjects<CoilSystemCoolingWater>().size());
EXPECT_EQ(1, m.getConcreteModelObjects<CoilSystemCoolingWaterHeatExchangerAssisted>().size());
EXPECT_EQ(2, m.getConcreteModelObjects<CoilCoolingWater>().size());

ASSERT_TRUE(coilSystem.airLoopHVAC());
EXPECT_EQ(coilSystem.airLoopHVAC()->handle(), a.handle());

EXPECT_TRUE(coolingCoil.plantLoop());
EXPECT_TRUE(companionCoil.plantLoop());

EXPECT_FALSE(coolingCoil.controllerWaterCoil());
EXPECT_FALSE(companionCoil.controllerWaterCoil());
EXPECT_EQ(0, m.getConcreteModelObjects<ControllerWaterCoil>().size());

auto coilSystemClone = coilSystem.clone(m).cast<CoilSystemCoolingWater>();

EXPECT_EQ(2, m.getConcreteModelObjects<CoilSystemCoolingWater>().size());
EXPECT_EQ(2, m.getConcreteModelObjects<CoilSystemCoolingWaterHeatExchangerAssisted>().size());
EXPECT_EQ(4, m.getConcreteModelObjects<CoilCoolingWater>().size());
EXPECT_EQ(0, m.getConcreteModelObjects<ControllerWaterCoil>().size());

EXPECT_TRUE(coilSystem.airLoopHVAC());
EXPECT_TRUE(coilSystem.inletModelObject());
EXPECT_TRUE(coilSystem.outletModelObject());

EXPECT_FALSE(coilSystemClone.airLoopHVAC());
EXPECT_FALSE(coilSystemClone.inletModelObject());
EXPECT_FALSE(coilSystemClone.outletModelObject());

EXPECT_TRUE(coilSystemClone.coolingCoil().optionalCast<CoilSystemCoolingWaterHeatExchangerAssisted>());
EXPECT_NE(coilSystemHXAssisted.handle(), coilSystemClone.coolingCoil().handle());

ASSERT_TRUE(coilSystemClone.companionCoilUsedForHeatRecovery());
EXPECT_TRUE(coilSystemClone.companionCoilUsedForHeatRecovery()->optionalCast<CoilCoolingWater>());
EXPECT_NE(companionCoil.handle(), coilSystemClone.companionCoilUsedForHeatRecovery()->handle());

// Clone into another Model
{
Model m2;
auto coilSystemClone = coilSystem.clone(m2).cast<CoilSystemCoolingWater>();

EXPECT_EQ(1, m2.getConcreteModelObjects<CoilSystemCoolingWater>().size());
EXPECT_EQ(1, m2.getConcreteModelObjects<CoilSystemCoolingWaterHeatExchangerAssisted>().size());
EXPECT_EQ(2, m2.getConcreteModelObjects<CoilCoolingWater>().size());
EXPECT_EQ(0, m2.getConcreteModelObjects<ControllerWaterCoil>().size());

EXPECT_FALSE(coilSystemClone.airLoopHVAC());
EXPECT_FALSE(coilSystemClone.inletModelObject());
EXPECT_FALSE(coilSystemClone.outletModelObject());

EXPECT_TRUE(coilSystemClone.coolingCoil().optionalCast<CoilSystemCoolingWaterHeatExchangerAssisted>());
EXPECT_NE(coilSystemHXAssisted.handle(), coilSystemClone.coolingCoil().handle());

ASSERT_TRUE(coilSystemClone.companionCoilUsedForHeatRecovery());
EXPECT_TRUE(coilSystemClone.companionCoilUsedForHeatRecovery()->optionalCast<CoilCoolingWater>());
EXPECT_NE(companionCoil.handle(), coilSystemClone.companionCoilUsedForHeatRecovery()->handle());

// Remove
coilSystemClone.remove();
EXPECT_EQ(0, m2.getConcreteModelObjects<CoilSystemCoolingWater>().size());
EXPECT_EQ(0, m2.getConcreteModelObjects<CoilSystemCoolingWaterHeatExchangerAssisted>().size());
EXPECT_EQ(0, m2.getConcreteModelObjects<CoilCoolingWater>().size());
}

coilSystemClone.remove();
EXPECT_EQ(1, m.getConcreteModelObjects<CoilSystemCoolingWater>().size());
EXPECT_EQ(1, m.getConcreteModelObjects<CoilSystemCoolingWaterHeatExchangerAssisted>().size());
EXPECT_EQ(2, m.getConcreteModelObjects<CoilCoolingWater>().size());
EXPECT_EQ(0, m.getConcreteModelObjects<ControllerWaterCoil>().size());

coilSystem.remove();
EXPECT_EQ(0, m.getConcreteModelObjects<CoilSystemCoolingWater>().size());
EXPECT_EQ(0, m.getConcreteModelObjects<CoilSystemCoolingWaterHeatExchangerAssisted>().size());
EXPECT_EQ(0, m.getConcreteModelObjects<CoilCoolingWater>().size());
EXPECT_EQ(0, m.getConcreteModelObjects<ControllerWaterCoil>().size());

EXPECT_EQ(3, a.supplyComponents().size()); // o --- oa_sys --- o
EXPECT_EQ(1, oa_sys.oaComponents().size()); // o
EXPECT_EQ(1, oa_sys.reliefComponents().size()); // o
EXPECT_EQ(5, p.demandComponents().size()); // o --- Splitter --- o --- Mixer --- o
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extensive test for clone and remove. Lots were going wrong, and p.demandComponents was 0 because the connections were broken.

…ode (and getting them out of sync) in both ctors
@jmarrec jmarrec mentioned this pull request May 5, 2025
19 tasks
jmarrec added 4 commits May 5, 2025 18:04
…has a FIXME (it fails)

 /home/julien/Software/Others/OpenStudio/src/energyplus/Test/CoilSystemCoolingWater_GTest.cpp:297: Failure
Expected equality of these values:
  "Relief Node"
  idf_companionCoil.getString(Coil_Cooling_WaterFields::AirOutletNodeName).get()
    Which is: "CompanionCoilUsedForHeatRecovery Exhaust Outlet Node"
Comment on lines +77 to +80
} else if (idf->iddObject().type() == IddObjectType::CoilSystem_Cooling_Water_HeatExchangerAssisted) {
// The logic is built in the translateCoilSystemCoolingWaterHeatExchangerAssisted method
// Essentially it's the same as getting the HX (CoilSystem_Cooling_Water_HeatExchangerAssistedFields::HeatExchangerName)
// and setting HeatExchanger_AirToAir_SensibleAndLatentFields::SupplyAirInletNodeName & ExhaustAirOutletNodeName
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We gotta handle the CoilSystem_Cooling_Water_HeatExchangerAssisted case. I'm not doing it here because it's too complicated/awkward because we work with IdfObject, not WorkspaceObject, so theree isn't a getTarget available to set the HX Nodes

Comment on lines +52 to +75
std::string hxExhaustAirOutletNodeName;
// InletNodeName
if (auto mo = modelObject.inletModelObject()) {
if (auto node = mo->optionalCast<Node>()) {
hxSupplyAirInletNodeName = node->nameString();
}
}

std::string hxExhaustAirOutletNodeName;
// OutletNodeName
if (auto mo = modelObject.outletModelObject()) {
if (auto node = mo->optionalCast<Node>()) {
hxExhaustAirOutletNodeName = node->nameString();
// OutletNodeName
if (auto mo = modelObject.outletModelObject()) {
if (auto node = mo->optionalCast<Node>()) {
hxExhaustAirOutletNodeName = node->nameString();
}
}
} else if (auto comp_ = modelObject.containingHVACComponent()) {
if (auto coilSystem_ = comp_->optionalCast<CoilSystemCoolingWater>()) {
if (auto mo = coilSystem_->inletModelObject()) {
if (auto node = mo->optionalCast<Node>()) {
hxSupplyAirInletNodeName = node->nameString();
}
}
if (auto mo = coilSystem_->outletModelObject()) {
if (auto node = mo->optionalCast<Node>()) {
hxExhaustAirOutletNodeName = node->nameString();
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CoilSystem_Cooling_Water_HeatExchangerAssisted: when translating it, we check if there's a inletModelobject, in which case this is directly added onto an AirLoopHVAC.

Otherwise, get the inlet/outlet air nodes from the parent CoilSystemCoolingWater object if any

Comment on lines +88 to +89
// TODO: is that true?!
// Need a SPM:MixedAir on the outlet node (that we **created** just above in IDF directly, so it won't get picked up by the
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joseph-robertson not sure if that's just copy pasted fromCoilSystem_Cooling_Water_HeatExchangerAssisted or actually required.

Comment on lines +176 to +190
if (boost::optional<HVACComponent> companionCoilUsedForHeatRecovery_ = modelObject.companionCoilUsedForHeatRecovery()) {
if (auto comp_ = companionCoilUsedForHeatRecovery_->optionalCast<CoilCoolingWater>()) {
if (comp_->airInletModelObject()) {
if (boost::optional<IdfObject> idf_ = translateAndMapModelObject(companionCoilUsedForHeatRecovery_.get())) {
idfObject.setString(CoilSystem_Cooling_WaterFields::CompanionCoilUsedForHeatRecovery, idf_->nameString());
}
} else {
// Shouldn't happen, accepts only Coil:Cooling:Water or Coil:Cooling:Water:DetailedGeometry
// Shouldn't happen, accepts only Coil:Cooling:DX:SingleSpeed or Coil:Cooling:DX:VariableSpeed
LOG(Fatal, modelObject.briefDescription() << " appears to have a companion coil used for heat recovery that shouldn't have been accepted: "
<< companionCoilUsedForHeatRecovery_.get().briefDescription());
OS_ASSERT(false);
LOG(Warn, modelObject.briefDescription()
<< " has a companion coil used for heat recovery that is not connected to an AirLoopHVAC / AirLoopHVACOutdoorAirSystem"
<< " and will not be translated:" << comp_->briefDescription());
}
} else {
// Shouldn't happen, accepts only Coil:Cooling:Water
LOG_AND_THROW(modelObject.briefDescription() << " appears to have a companion coil used for heat recovery that shouldn't have been accepted: "
<< companionCoilUsedForHeatRecovery_.get().briefDescription());
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No translating the companion coil if it's not connected by itself on an AirLoopHVAC/AirLoopHVACOutdoorAIrSystem

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I improved the testing for the cases you wrote, and I added a new one for CoilSystem_Cooling_Water_HeatExchangerAssisted

@jmarrec
Copy link
Collaborator Author

jmarrec commented May 5, 2025

@joseph-robertson I'm going to merge this one back into yours so I can get (one) CI to run on it, but please review carefully.

@jmarrec jmarrec merged commit 97e0142 into coilsystem-cooling-water May 5, 2025
@jmarrec jmarrec deleted the coilsystem-cooling-water2 branch May 5, 2025 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants