Fix #4656 - E+ 22.2.0: Wrap Chiller:Electric:ASHRAE205#4687
Fix #4656 - E+ 22.2.0: Wrap Chiller:Electric:ASHRAE205#4687jmarrec merged 24 commits intov22.2.0-IOFreezefrom
Conversation
``` $os_build3/Products/openstudio GenerateClass.rb -c "ChillerElectricASHRAE205" -b "WaterToWaterComponent" -i "OS:Chiller:Electric:ASHRAE205" -s model -o /media/DataExt4/Software/Others/OpenStudio3/src/model/ -p -f -r ```
… a Node OutdoorAir:Node isn't wrapped in OS SDK
…hod to reset the oil /auxiliary ports too
… not a ThermalZone
…ated, a Space is returned not a ThermalZone!
…WICE on the same branch
…o the representation filename
… we need to check by inlet node!
| Chiller:Electric:ASHRAE205, | ||
| \min-fields 11 | ||
| \memo This chiller model utilizes ASHRAE Standard 205 compliant representations | ||
| \memo for chillers (Representation Specification RS0001). | ||
| A1, \field Name | ||
| \type alpha | ||
| \reference Chillers | ||
| \required-field | ||
| \reference-class-name validPlantEquipmentTypes | ||
| \reference validPlantEquipmentNames | ||
| \reference-class-name validBranchEquipmentTypes | ||
| \reference validBranchEquipmentNames | ||
| A2, \field Representation File Name | ||
| \note The name of the ASHRAE205 RS0001 (chiller) representation file | ||
| \type alpha | ||
| \retaincase | ||
| \required-field | ||
| A3, \field Performance Interpolation Method | ||
| \type choice | ||
| \key Linear | ||
| \key Cubic | ||
| \default Linear | ||
| N1, \field Rated Capacity | ||
| \note Not yet implemented / reserved for future use. Full load capacity at AHRI 550/590 test conditions. | ||
| \note Used to scale representation data. | ||
| \type real | ||
| \units W | ||
| \minimum> 0.0 | ||
| \autosizable | ||
| \default autosize | ||
| N2, \field Sizing Factor | ||
| \note Multiplies the autosized flow rates. | ||
| \type real | ||
| \minimum> 0.0 | ||
| \default 1.0 | ||
| A4 , \field Ambient Temperature Indicator | ||
| \note Used to determine standby losses | ||
| \required-field | ||
| \type choice | ||
| \key Schedule | ||
| \key Zone | ||
| \key Outdoors | ||
| A5 , \field Ambient Temperature Schedule Name | ||
| \type object-list | ||
| \object-list ScheduleNames | ||
| A6, \field Ambient Temperature Zone Name | ||
| \note Any energy imbalance on the chiller results in heat added to this zone. | ||
| \type object-list | ||
| \object-list ZoneNames | ||
| A7, \field Ambient Temperature Outdoor Air Node Name | ||
| \type node | ||
| \note required for Ambient Temperature Indicator=Outdoors | ||
| A8, \field Chilled Water Inlet Node Name | ||
| \type node | ||
| \required-field | ||
| A9, \field Chilled Water Outlet Node Name | ||
| \type node | ||
| \required-field | ||
| A10, \field Chilled Water Maximum Requested Flow Rate | ||
| \type real | ||
| \units m3/s | ||
| \minimum> 0 | ||
| \autosizable | ||
| \ip-units gal/min | ||
| \default autosize | ||
| A11, \field Condenser Inlet Node Name | ||
| \type node | ||
| A12, \field Condenser Outlet Node Name | ||
| \type node | ||
| A13, \field Condenser Maximum Requested Flow Rate | ||
| \type real | ||
| \units m3/s | ||
| \minimum> 0 | ||
| \autosizable | ||
| \ip-units gal/min | ||
| \default autosize | ||
| A14, \field Chiller Flow Mode | ||
| \note Select operating mode for fluid flow through the chiller. "NotModulated" is for | ||
| \note either variable or constant pumping with flow controlled by the external plant system. | ||
| \note "ConstantFlow" is for constant pumping with flow controlled by chiller to operate at | ||
| \note full design flow rate. "LeavingSetpointModulated" is for variable pumping with flow | ||
| \note controlled by chiller to vary flow to target a leaving temperature setpoint. | ||
| \type choice | ||
| \key ConstantFlow | ||
| \key LeavingSetpointModulated | ||
| \key NotModulated | ||
| \default NotModulated | ||
| A15, \field Oil Cooler Inlet Node Name | ||
| \note Use if the oil cooler uses an external cooling loop, otherwise the oil cooler will add | ||
| \note heat to the ambient conditions (i.e., it is air cooled). | ||
| \type node | ||
| A16, \field Oil Cooler Outlet Node Name | ||
| \type node | ||
| A17, \field Oil Cooler Design Flow Rate | ||
| \type real | ||
| \units m3/s | ||
| \minimum> 0 | ||
| \ip-units gal/min | ||
| A18, \field Auxiliary Inlet Node Name | ||
| \note Use if the auxiliary components of the chiller use an external cooling loop, otherwise | ||
| \note the auxiliary components will add heat to the ambient conditions (i.e., they are air cooled). | ||
| \type node | ||
| A19, \field Auxiliary Outlet Node Name | ||
| \type node | ||
| A20, \field Auxiliary Cooling Design Flow Rate | ||
| \type real | ||
| \units m3/s | ||
| \minimum> 0 | ||
| \ip-units gal/min | ||
| A21, \field Heat Recovery Inlet Node Name | ||
| \note Not yet implemented / reserved for future use. Heat recovery is not yet within scope of ASHRAE Stanard 205. | ||
| \type node | ||
| A21, \field Heat Recovery Outlet Node Name | ||
| \note Not yet implemented / reserved for future use. Heat recovery is not yet within scope of ASHRAE Stanard 205. | ||
| \type node | ||
| A23; \field End-Use Subcategory | ||
| \note Any text may be used here to categorize the end-uses in the ABUPS End Uses by Subcategory table. | ||
| \type alpha | ||
| \retaincase | ||
| \default General |
There was a problem hiding this comment.
New IDD object. Everything is required-field as much as possible.
NOTE:
- This object will eventually have FIVE loops: chilled water, condenser, heat recovery, oil cooler, and auxiliary
- Heat Recovery is reserved as a Tertiary connection, but it's disabled right now in model SDK due to the fact that E+ doesn't implement it yet.
| // chilledWaterLoop | ||
| virtual boost::optional<PlantLoop> plantLoop() const override; | ||
| virtual unsigned supplyInletPort() const override; | ||
| virtual unsigned supplyOutletPort() const override; | ||
|
|
||
| // condenserWaterLoop | ||
| virtual boost::optional<PlantLoop> secondaryPlantLoop() const override; | ||
| virtual unsigned demandInletPort() const override; | ||
| virtual unsigned demandOutletPort() const override; | ||
|
|
||
| // heatRecoveryLoop | ||
| virtual boost::optional<PlantLoop> tertiaryPlantLoop() const override; | ||
| virtual unsigned tertiaryInletPort() const override; | ||
| virtual unsigned tertiaryOutletPort() const override; | ||
|
|
||
| virtual std::vector<HVACComponent> edges(const boost::optional<HVACComponent>& prev) override; | ||
|
|
||
| /** This function will perform a check if trying to add it to a node that is on the demand side of a plant loop. | ||
| * If: | ||
| * - the node is on the demand side of a loop | ||
| * - the node isn't on the current condenser water loop itself | ||
| * - the chiller doesn't already have a heat recovery (tertiary) loop, | ||
| * then it tries to add it to the Tertiary loop. | ||
| * In all other cases, it will call the base class' method WaterToWaterComponent_Impl::addToNode() | ||
| * If this is connecting to the demand side of a loop (not tertiary), will set the chiller condenserType to WaterCooled | ||
| */ | ||
| virtual bool addToNode(Node& node) override; | ||
|
|
||
| /* Restricts addToTertiaryNode to a node that is on the demand side of a plant loop (tertiary = Heat Recovery Loop) */ | ||
| virtual bool addToTertiaryNode(Node& node) override; |
There was a problem hiding this comment.
I have to override pretty much everything due to the fact that this is the first object with more than 3 loops...
| bool ChillerElectricASHRAE205_Impl::addToTertiaryNode(Node& node) { | ||
|
|
||
| if constexpr (!ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) { | ||
| LOG(Warn, "For " << briefDescription() << ", Heat Recovery isn't implemented by EnergyPlus yet, so tertiary connection is disabled."); | ||
| return false; | ||
| } | ||
|
|
||
| auto t_plantLoop = node.plantLoop(); | ||
|
|
||
| // Only accept adding to a node that is on a demand side of a plant loop | ||
| // Since tertiary here = heat recovery loop (heating) | ||
| if (t_plantLoop) { | ||
| if (t_plantLoop->demandComponent(node.handle())) { | ||
| // Call base class method which accepts both supply and demand | ||
| return WaterToWaterComponent_Impl::addToTertiaryNode(node); | ||
| } else { | ||
| LOG(Info, | ||
| "Tertiary Loop (Heat Recovery Loop) connections can only be placed on the Demand side (of a Heating Loop), for " << briefDescription()); | ||
| } | ||
| } | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Disabled for now, but I have done that last to make sure it works correctly anyways.
| std::vector<HVACComponent> ChillerElectricASHRAE205_Impl::edges(const boost::optional<HVACComponent>& prev) { | ||
| // This handles supply, demand, and tertiary connections | ||
| std::vector<HVACComponent> edges = WaterToWaterComponent_Impl::edges(prev); | ||
|
|
||
| auto pushOilCoolerOutletModelObject = [&]() { | ||
| if (auto edgeModelObject = oilCoolerOutletModelObject()) { | ||
| auto edgeHVACComponent = edgeModelObject->optionalCast<HVACComponent>(); | ||
| OS_ASSERT(edgeHVACComponent); | ||
| edges.push_back(edgeHVACComponent.get()); | ||
| } | ||
| }; | ||
|
|
||
| auto pushAuxiliaryOutletModelObject = [&]() { | ||
| if (auto edgeModelObject = auxiliaryOutletModelObject()) { | ||
| auto edgeHVACComponent = edgeModelObject->optionalCast<HVACComponent>(); | ||
| OS_ASSERT(edgeHVACComponent); | ||
| edges.push_back(edgeHVACComponent.get()); | ||
| } | ||
| }; | ||
|
|
||
| if (prev) { | ||
| if (auto inletModelObject = oilCoolerInletModelObject()) { | ||
| if (prev.get() == inletModelObject.get()) { | ||
| pushOilCoolerOutletModelObject(); | ||
| return edges; | ||
| } | ||
| } | ||
| if (auto inletModelObject = auxiliaryInletModelObject()) { | ||
| if (prev.get() == inletModelObject.get()) { | ||
| pushAuxiliaryOutletModelObject(); | ||
| return edges; | ||
| } | ||
| } | ||
| } else { | ||
| pushOilCoolerOutletModelObject(); | ||
| pushAuxiliaryOutletModelObject(); | ||
| return edges; | ||
| } | ||
|
|
||
| return edges; | ||
| } |
There was a problem hiding this comment.
I need to reimplement edges to handle the extra loops
| boost::optional<PlantLoop> ChillerElectricASHRAE205_Impl::secondaryPlantLoop() const { | ||
| if (boost::optional<ModelObject> mo_ = demandOutletModelObject()) { | ||
| if (boost::optional<Node> n_ = mo_->optionalCast<Node>()) { | ||
| return n_->plantLoop(); | ||
| } | ||
| } | ||
| return boost::none; | ||
| } |
There was a problem hiding this comment.
I need to override this one, because by default WaterToWaterComponent will just find a PlantLoop that has it on demand and is not the tertiary... But this object can be on the demand side of FOUR loops
| // Heat Recovery | ||
| if (auto node_ = modelObject.heatRecoveryInletNode()) { | ||
| idfObject.setString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName, node_->nameString()); | ||
| } | ||
|
|
||
| if (auto node_ = modelObject.heatRecoveryOutletNode()) { | ||
| idfObject.setString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName, node_->nameString()); | ||
| } |
There was a problem hiding this comment.
I'm leaving this but it'll never be used for now (since the Heat Recovery connection is disabled at model time)
| // Ambient Temperature Zone Name: Optional Object | ||
| if (auto wo_z_ = workspaceObject.getTarget(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName)) { | ||
| if (auto mo_ = translateAndMapWorkspaceObject(wo_z_.get())) { | ||
| // Zone is translated, and a Space is returned instead | ||
| if (boost::optional<Space> space_ = mo_->optionalCast<Space>()) { | ||
| if (auto z_ = space_->thermalZone()) { | ||
| modelObject.setAmbientTemperatureZone(z_.get()); | ||
| } | ||
| } else { | ||
| LOG(Warn, workspaceObject.briefDescription() << " has a wrong type for 'Ambient Temperature Zone Name'"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Initially I messed up and didn't realize that translateAndMapWorkspaceObject for a Zone returns a Space.
| if (auto mo_ = translateAndMapWorkspaceObject(*target)) { | ||
| // Zone is translated, and a Space is returned instead | ||
| if (boost::optional<Space> space_ = mo_->optionalCast<Space>()) { | ||
| if (auto z_ = space_->thermalZone()) { | ||
| dx.setCondenserZone(z_.get()); | ||
| } | ||
| } else { | ||
| LOG(Warn, workspaceObject.briefDescription() << " has a wrong type for 'Condenser Zone Name'"); |
There was a problem hiding this comment.
I found this same issue in a bunch of existing RT files so I fixed those I found.
| /* Ensures that the nodes that translated correctly | ||
| * that means correct node names in the ChillerElectricASHRAE205 but also | ||
| * on the Branches | ||
| */ | ||
| TEST_F(EnergyPlusFixture, ForwardTranslator_ChillerElectricASHRAE205) { | ||
|
|
||
| ForwardTranslator ft; | ||
|
|
||
| Model m; | ||
|
|
||
| auto createLoop = [&m](const std::string& prefix) { | ||
| PlantLoop p(m); | ||
| static constexpr std::array<std::string_view, 10> compNames = { | ||
| "Supply Inlet", "Supply Splitter", "Supply Connection Node", "Supply Mixer", "Supply Outlet", | ||
| "Demand Inlet", "Demand Splitter", "Demand Connection Node", "Demand Mixer", "Demand Outlet", | ||
| }; | ||
| p.setName(prefix); | ||
| for (size_t i = 0; auto& comp : p.components()) { | ||
| comp.setName(prefix + " " + std::string{compNames[i++]}); | ||
| } | ||
| return p; | ||
| }; | ||
|
|
||
| openstudio::path p = resourcesPath() / toPath("model/A205ExampleChiller.RS0001.a205.cbor"); | ||
| EXPECT_TRUE(exists(p)); | ||
|
|
||
| boost::optional<ExternalFile> representationFile = ExternalFile::getExternalFile(m, openstudio::toString(p)); | ||
| ASSERT_TRUE(representationFile); | ||
|
|
||
| ChillerElectricASHRAE205 ch(representationFile.get()); | ||
|
|
||
| EXPECT_TRUE(ch.setPerformanceInterpolationMethod("Cubic")); | ||
| ch.autosizeRatedCapacity(); | ||
| EXPECT_TRUE(ch.setSizingFactor(1.1)); | ||
|
|
||
| ThermalZone z(m); | ||
| z.setName("Basement"); | ||
| { | ||
| std::vector<Point3d> sPoints{{0, 0, 0}, {0, 1, 0}, {1, 1, 0}, {1, 0, 0}}; | ||
| auto space_ = Space::fromFloorPrint(sPoints, 3.0, m); | ||
| ASSERT_TRUE(space_); | ||
| EXPECT_TRUE(space_->setThermalZone(z)); | ||
| } | ||
|
|
||
| EXPECT_TRUE(ch.setAmbientTemperatureZone(z)); | ||
|
|
||
| EXPECT_TRUE(ch.setChilledWaterMaximumRequestedFlowRate(0.0428)); | ||
|
|
||
| auto chwLoop = createLoop("chwLoop"); | ||
| EXPECT_TRUE(chwLoop.addSupplyBranchForComponent(ch)); | ||
|
|
||
| auto cndLoop = createLoop("cndLoop"); | ||
| EXPECT_TRUE(cndLoop.addDemandBranchForComponent(ch)); | ||
| EXPECT_TRUE(ch.setCondenserMaximumRequestedFlowRate(0.0552)); | ||
|
|
||
| EXPECT_TRUE(ch.setChillerFlowMode("ConstantFlow")); | ||
|
|
||
| auto ocLoop = createLoop("ocLoop"); | ||
| EXPECT_TRUE(ch.addDemandBranchOnOilCoolerLoop(ocLoop)); | ||
| EXPECT_TRUE(ch.setOilCoolerDesignFlowRate(0.001)); | ||
|
|
||
| auto auxLoop = createLoop("auxLoop"); | ||
| EXPECT_TRUE(ch.addDemandBranchOnAuxiliaryLoop(auxLoop)); | ||
| EXPECT_TRUE(ch.setAuxiliaryCoolingDesignFlowRate(0.002)); | ||
|
|
||
| EXPECT_TRUE(ch.setEndUseSubcategory("Chiller")); | ||
|
|
||
| ch.chilledWaterInletNode()->setName("ChilledWater Inlet Node"); | ||
| ch.chilledWaterOutletNode()->setName("ChilledWater Outlet Node"); | ||
| ch.condenserInletNode()->setName("Condenser Inlet Node"); | ||
| ch.condenserOutletNode()->setName("Condenser Outlet Node"); | ||
|
|
||
| ch.oilCoolerInletNode()->setName("OilCooler Inlet Node"); | ||
| ch.oilCoolerOutletNode()->setName("OilCooler Outlet Node"); | ||
| ch.auxiliaryInletNode()->setName("Auxiliary Inlet Node"); | ||
| ch.auxiliaryOutletNode()->setName("Auxiliary Outlet Node"); | ||
|
|
||
| if constexpr (ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) { | ||
| auto hrLoop = createLoop("hrLoop"); | ||
| EXPECT_TRUE(hrLoop.addDemandBranchForComponent(ch, true)); | ||
| ch.heatRecoveryInletNode()->setName("HeatRecovery Inlet Node"); | ||
| ch.heatRecoveryOutletNode()->setName("HeatRecovery Outlet Node"); | ||
| } | ||
|
|
||
| { | ||
| Workspace w = ft.translateModel(m); | ||
|
|
||
| std::vector<WorkspaceObject> woChs = w.getObjectsByType(IddObjectType::Chiller_Electric_ASHRAE205); | ||
| ASSERT_EQ(1, woChs.size()); | ||
| auto& woCh = woChs.front(); | ||
|
|
||
| EXPECT_EQ(ch.nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::Name).get()); | ||
| EXPECT_EQ(representationFile->filePath(), woCh.getString(Chiller_Electric_ASHRAE205Fields::RepresentationFileName).get()); | ||
| EXPECT_EQ("Cubic", woCh.getString(Chiller_Electric_ASHRAE205Fields::PerformanceInterpolationMethod).get()); | ||
| EXPECT_EQ("Autosize", woCh.getString(Chiller_Electric_ASHRAE205Fields::RatedCapacity).get()); | ||
| EXPECT_EQ(1.1, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::SizingFactor).get()); | ||
|
|
||
| EXPECT_EQ("Zone", woCh.getString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureIndicator).get()); | ||
| EXPECT_TRUE(woCh.isEmpty(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureScheduleName)); | ||
| EXPECT_EQ("Basement", woCh.getString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName).get()); | ||
| EXPECT_TRUE(woCh.isEmpty(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureOutdoorAirNodeName)); | ||
|
|
||
| EXPECT_EQ(0.0428, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate).get()); | ||
|
|
||
| EXPECT_EQ(0.0552, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate).get()); | ||
|
|
||
| EXPECT_EQ("ConstantFlow", woCh.getString(Chiller_Electric_ASHRAE205Fields::ChillerFlowMode).get()); | ||
|
|
||
| EXPECT_EQ(0.001, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::OilCoolerDesignFlowRate).get()); | ||
|
|
||
| EXPECT_EQ(0.002, woCh.getDouble(Chiller_Electric_ASHRAE205Fields::AuxiliaryCoolingDesignFlowRate).get()); | ||
|
|
||
| EXPECT_EQ("Chiller", woCh.getString(Chiller_Electric_ASHRAE205Fields::EndUseSubcategory).get()); | ||
|
|
||
| EXPECT_EQ("ChilledWater Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::ChilledWaterInletNodeName).get()); | ||
| EXPECT_EQ("ChilledWater Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::ChilledWaterOutletNodeName).get()); | ||
| EXPECT_EQ(ch.chilledWaterInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::ChilledWaterInletNodeName).get()); | ||
| EXPECT_EQ(ch.chilledWaterOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::ChilledWaterOutletNodeName).get()); | ||
|
|
||
| EXPECT_EQ("Condenser Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::CondenserInletNodeName).get()); | ||
| EXPECT_EQ("Condenser Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::CondenserOutletNodeName).get()); | ||
| EXPECT_EQ(ch.condenserInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::CondenserInletNodeName).get()); | ||
| EXPECT_EQ(ch.condenserOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::CondenserOutletNodeName).get()); | ||
|
|
||
| EXPECT_EQ("OilCooler Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::OilCoolerInletNodeName).get()); | ||
| EXPECT_EQ("OilCooler Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::OilCoolerOutletNodeName).get()); | ||
| EXPECT_EQ(ch.oilCoolerInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::OilCoolerInletNodeName).get()); | ||
| EXPECT_EQ(ch.oilCoolerOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::OilCoolerOutletNodeName).get()); | ||
|
|
||
| EXPECT_EQ("Auxiliary Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::AuxiliaryInletNodeName).get()); | ||
| EXPECT_EQ("Auxiliary Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::AuxiliaryOutletNodeName).get()); | ||
| EXPECT_EQ(ch.auxiliaryInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::AuxiliaryInletNodeName).get()); | ||
| EXPECT_EQ(ch.auxiliaryOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::AuxiliaryOutletNodeName).get()); | ||
|
|
||
| if constexpr (ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) { | ||
| EXPECT_EQ("HeatRecovery Inlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName).get()); | ||
| EXPECT_EQ("HeatRecovery Outlet Node", woCh.getString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName).get()); | ||
| EXPECT_EQ(ch.heatRecoveryInletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName).get()); | ||
| EXPECT_EQ(ch.heatRecoveryOutletNode()->nameString(), woCh.getString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName).get()); | ||
| } | ||
|
|
||
| // Check node names on supply/demand branches | ||
| // Checks that the special case implemented in ForwardTranslatePlantLoop::populateBranch does the right job | ||
|
|
||
| struct Expected | ||
| { | ||
| bool isSupply = true; | ||
| std::string plantName; | ||
| std::string inletNodeName; | ||
| std::string outletNodeName; | ||
| }; | ||
|
|
||
| std::vector<Expected> expecteds = { | ||
| {true, ch.chilledWaterLoop()->nameString(), ch.chilledWaterInletNode()->nameString(), ch.chilledWaterOutletNode()->nameString()}, | ||
| {false, ch.condenserWaterLoop()->nameString(), ch.condenserInletNode()->nameString(), ch.condenserOutletNode()->nameString()}, | ||
| {false, ch.oilCoolerLoop()->nameString(), ch.oilCoolerInletNode()->nameString(), ch.oilCoolerOutletNode()->nameString()}, | ||
| {false, ch.auxiliaryLoop()->nameString(), ch.auxiliaryInletNode()->nameString(), ch.auxiliaryOutletNode()->nameString()}, | ||
| }; | ||
|
|
||
| if constexpr (ChillerElectricASHRAE205::isHeatRecoverySupportedByEnergyplus) { | ||
| expecteds.emplace_back(false, ch.heatRecoveryLoop()->nameString(), ch.heatRecoveryInletNode()->nameString(), | ||
| ch.heatRecoveryOutletNode()->nameString()); | ||
| } | ||
|
|
||
| for (const auto& e : expecteds) { | ||
| auto p_ = w.getObjectByTypeAndName(IddObjectType::PlantLoop, e.plantName); | ||
| ASSERT_TRUE(p_.is_initialized()) << "Cannot find PlantLoop named " << e.plantName; | ||
| WorkspaceObject idf_plant = p_.get(); | ||
| unsigned index = e.isSupply ? PlantLoopFields::PlantSideBranchListName : PlantLoopFields::DemandSideBranchListName; | ||
| WorkspaceObject idf_brlist = idf_plant.getTarget(index).get(); | ||
|
|
||
| // Should have at least three branches: supply inlet, the one with the Chiller, supply outlet. | ||
| // On the demand side, there's also a bypass branch that is added by the FT by default | ||
| ASSERT_EQ(e.isSupply ? 3 : 4, idf_brlist.extensibleGroups().size()) << "Failed for " << e.plantName; | ||
| // Get the Chiller one | ||
| auto w_eg = idf_brlist.extensibleGroups()[1].cast<WorkspaceExtensibleGroup>(); | ||
| WorkspaceObject idf_branch = w_eg.getTarget(BranchListExtensibleFields::BranchName).get(); | ||
|
|
||
| // There should be only one equipment on the branch | ||
| ASSERT_EQ(1, idf_branch.extensibleGroups().size()); | ||
| auto w_eg2 = idf_branch.extensibleGroups()[0].cast<WorkspaceExtensibleGroup>(); | ||
|
|
||
| ASSERT_EQ(w_eg2.getString(BranchExtensibleFields::ComponentName).get(), ch.nameString()); | ||
| ASSERT_EQ(w_eg2.getString(BranchExtensibleFields::ComponentInletNodeName).get(), e.inletNodeName); | ||
| ASSERT_EQ(w_eg2.getString(BranchExtensibleFields::ComponentOutletNodeName).get(), e.outletNodeName); | ||
|
|
||
| WorkspaceObject idf_plant_op = p_->getTarget(PlantLoopFields::PlantEquipmentOperationSchemeName).get(); | ||
| if (e.isSupply) { | ||
| // Should have created a Cooling Load one only | ||
| ASSERT_EQ(1, idf_plant_op.extensibleGroups().size()); | ||
| auto w_eg = idf_plant_op.extensibleGroups()[0].cast<WorkspaceExtensibleGroup>(); | ||
| ASSERT_EQ("PlantEquipmentOperation:CoolingLoad", | ||
| w_eg.getString(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeObjectType).get()); | ||
|
|
||
| // Get the Operation Scheme | ||
| auto op_scheme_ = w_eg.getTarget(PlantEquipmentOperationSchemesExtensibleFields::ControlSchemeName); | ||
| ASSERT_TRUE(op_scheme_); | ||
|
|
||
| // Get the Plant Equipment List of this CoolingLoad scheme | ||
| // There should only be one Load Range | ||
| ASSERT_EQ(1u, op_scheme_->extensibleGroups().size()); | ||
|
|
||
| // Load range 1 | ||
| w_eg = op_scheme_->extensibleGroups()[0].cast<WorkspaceExtensibleGroup>(); | ||
| auto peq_list_ = w_eg.getTarget(PlantEquipmentOperation_HeatingLoadExtensibleFields::RangeEquipmentListName); | ||
| ASSERT_TRUE(peq_list_); | ||
|
|
||
| // Should have one equipment on it: CentralHeatPumpSystem | ||
| auto peqs = peq_list_->extensibleGroups(); | ||
| ASSERT_EQ(1, peqs.size()); | ||
| ASSERT_EQ("Chiller:Electric:ASHRAE205", peqs.front().getString(PlantEquipmentListExtensibleFields::EquipmentObjectType).get()); | ||
| ASSERT_EQ(ch.nameString(), peqs.front().getString(PlantEquipmentListExtensibleFields::EquipmentName).get()); | ||
|
|
||
| } else { | ||
| EXPECT_EQ(0, idf_plant_op.extensibleGroups().size()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Not assigned to a Chilled Water Loop? not translated, it's required | ||
| ch.removeFromPlantLoop(); | ||
| { | ||
| // Assigned to a Surface | ||
| Workspace w = ft.translateModel(m); | ||
|
|
||
| EXPECT_EQ(0, w.getObjectsByType(IddObjectType::Chiller_Electric_ASHRAE205).size()); | ||
| } | ||
| } |
| TEST_F(EnergyPlusFixture, ReverseTranslator_ChillerElectricASHRAE205) { | ||
|
|
||
| ReverseTranslator rt; | ||
|
|
||
| Workspace w(StrictnessLevel::Minimal, IddFileType::EnergyPlus); | ||
|
|
||
| auto wo_zone = w.addObject(IdfObject(IddObjectType::Zone)).get(); | ||
| wo_zone.setName("Basement"); | ||
|
|
||
| auto woCh = w.addObject(IdfObject(IddObjectType::Chiller_Electric_ASHRAE205)).get(); | ||
| woCh.setName("Chiller ASHRAE205"); | ||
|
|
||
| openstudio::path p = resourcesPath() / toPath("model/A205ExampleChiller.RS0001.a205.cbor"); | ||
| EXPECT_TRUE(exists(p)); | ||
|
|
||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::RepresentationFileName, openstudio::toString(p))); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::PerformanceInterpolationMethod, "Cubic")); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::RatedCapacity, "")); // Defaults to Autosize | ||
| EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::SizingFactor, 1.1)); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureIndicator, "Zone")); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureScheduleName, "")); | ||
| EXPECT_TRUE(woCh.setPointer(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureZoneName, wo_zone.handle())); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AmbientTemperatureOutdoorAirNodeName, "")); | ||
| EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::ChilledWaterMaximumRequestedFlowRate, 0.0428)); | ||
| EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::CondenserMaximumRequestedFlowRate, 0.0552)); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::ChillerFlowMode, "ConstantFlow")); | ||
| EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::OilCoolerDesignFlowRate, 0.001)); | ||
| EXPECT_TRUE(woCh.setDouble(Chiller_Electric_ASHRAE205Fields::AuxiliaryCoolingDesignFlowRate, 0.002)); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::EndUseSubcategory, "Chiller")); | ||
|
|
||
| // Nodes not RT'ed | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::ChilledWaterInletNodeName, "ChilledWater Inlet Node")); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::ChilledWaterOutletNodeName, "ChilledWater Outlet Node")); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::CondenserInletNodeName, "Condenser Inlet Node")); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::CondenserOutletNodeName, "Condenser Outlet Node")); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::OilCoolerInletNodeName, "OilCooler Inlet Node")); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::OilCoolerOutletNodeName, "OilCooler Outlet Node")); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AuxiliaryInletNodeName, "Auxiliary Inlet Node")); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::AuxiliaryOutletNodeName, "Auxiliary Outlet Node")); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryInletNodeName, "HeatRecovery Inlet Node")); | ||
| EXPECT_TRUE(woCh.setString(Chiller_Electric_ASHRAE205Fields::HeatRecoveryOutletNodeName, "HeatRecovery Outlet Node")); | ||
|
|
||
| Model m = rt.translateWorkspace(w); | ||
|
|
||
| auto chs = m.getConcreteModelObjects<ChillerElectricASHRAE205>(); | ||
| ASSERT_EQ(1, chs.size()); | ||
| auto& ch = chs.front(); | ||
|
|
||
| ASSERT_TRUE(ch.name()); | ||
| EXPECT_EQ("Chiller ASHRAE205", ch.nameString()); | ||
| EXPECT_EQ("A205ExampleChiller.RS0001.a205.cbor", ch.representationFile().fileName()); | ||
| EXPECT_EQ("Cubic", ch.performanceInterpolationMethod()); | ||
| EXPECT_TRUE(ch.isRatedCapacityAutosized()); | ||
| EXPECT_EQ(1.1, ch.sizingFactor()); | ||
| EXPECT_EQ("Zone", ch.ambientTemperatureIndicator()); | ||
| EXPECT_FALSE(ch.ambientTemperatureSchedule()); | ||
| ASSERT_TRUE(ch.ambientTemperatureZone()); | ||
| EXPECT_EQ("Basement", ch.ambientTemperatureZone()->nameString()); | ||
| EXPECT_FALSE(ch.ambientTemperatureOutdoorAirNodeName()); | ||
|
|
||
| EXPECT_FALSE(ch.isChilledWaterMaximumRequestedFlowRateAutosized()); | ||
| ASSERT_TRUE(ch.chilledWaterMaximumRequestedFlowRate()); | ||
| EXPECT_EQ(0.0428, ch.chilledWaterMaximumRequestedFlowRate().get()); | ||
|
|
||
| EXPECT_FALSE(ch.isCondenserMaximumRequestedFlowRateAutosized()); | ||
| ASSERT_TRUE(ch.condenserMaximumRequestedFlowRate()); | ||
| EXPECT_EQ(0.0552, ch.condenserMaximumRequestedFlowRate().get()); | ||
|
|
||
| EXPECT_EQ("ConstantFlow", ch.chillerFlowMode()); | ||
|
|
||
| ASSERT_TRUE(ch.oilCoolerDesignFlowRate()); | ||
| EXPECT_EQ(0.001, ch.oilCoolerDesignFlowRate().get()); | ||
| ASSERT_TRUE(ch.auxiliaryCoolingDesignFlowRate()); | ||
| EXPECT_EQ(0.002, ch.auxiliaryCoolingDesignFlowRate().get()); | ||
|
|
||
| EXPECT_EQ("Chiller", ch.endUseSubcategory()); | ||
|
|
||
| EXPECT_FALSE(ch.chilledWaterLoop()); | ||
| EXPECT_FALSE(ch.condenserWaterLoop()); | ||
| EXPECT_FALSE(ch.heatRecoveryLoop()); | ||
| EXPECT_FALSE(ch.oilCoolerLoop()); | ||
| EXPECT_FALSE(ch.auxiliaryLoop()); | ||
| EXPECT_FALSE(ch.chilledWaterInletNode()); | ||
| EXPECT_FALSE(ch.chilledWaterOutletNode()); | ||
| EXPECT_FALSE(ch.condenserInletNode()); | ||
| EXPECT_FALSE(ch.condenserOutletNode()); | ||
| EXPECT_FALSE(ch.heatRecoveryInletNode()); | ||
| EXPECT_FALSE(ch.heatRecoveryOutletNode()); | ||
| EXPECT_FALSE(ch.oilCoolerInletNode()); | ||
| EXPECT_FALSE(ch.oilCoolerOutletNode()); | ||
| EXPECT_FALSE(ch.auxiliaryInletNode()); | ||
| EXPECT_FALSE(ch.auxiliaryOutletNode()); | ||
| } |
joseph-robertson
left a comment
There was a problem hiding this comment.
Pretty minimal review/questions from me - I think @kbenne would have more meaningful feedback re connections/loops, etc. One general thought I have is around eventual update for when heat recovery is available; this implementation wouldn't preclude future updates would it?
| \type choice | ||
| \key Linear | ||
| \key Cubic | ||
| \required-field |
There was a problem hiding this comment.
Using requireds 👍
| IdfObject idfObject = createRegisterAndNameIdfObject(openstudio::IddObjectType::Chiller_Electric_ASHRAE205, modelObject); | ||
|
|
||
| // Representation File Name: Required String | ||
| idfObject.setString(Chiller_Electric_ASHRAE205Fields::RepresentationFileName, openstudio::toString(filePath)); |
There was a problem hiding this comment.
Does anything ensure that the representation file is in the correct format?
There was a problem hiding this comment.
Nope. Just that is has a cbor extension. The parsing is not even done inside E+ directly but in an third_party lib. It'll throw at that point.
| case openstudio::IddObjectType::Chiller_Electric_ASHRAE205: { | ||
| modelObject = translateChillerElectricASHRAE205(workspaceObject); | ||
| break; | ||
| } |
There was a problem hiding this comment.
I was sort of under the impression that we tended to not RT HVAC components?
There was a problem hiding this comment.
Meh. There are others. I don't RT the node connections, only the rest.
| model/offset_tests.osm | ||
| model/ParkUnder_Retail_Office_C2.osm | ||
| model/ASHRAECourthouse.osm | ||
| model/A205ExampleChiller.RS0001.a205.cbor |
There was a problem hiding this comment.
How was this generated/obtained? Would we ever foresee it needing to get updated in the future?
There was a problem hiding this comment.
(this is an ExampleFiles when installed)
| {"AvailabilityManagerScheduledOff", "Availability Manager Scheduled Off", "schedule", false, "Availability", 0.0, 1.0}, | ||
| {"CentralHeatPumpSystem", "Ancillary Operation", "ancillaryOperationSchedule", false, "Availability", 0.0, 1.0}, | ||
| {"CentralHeatPumpSystemModule", "Chiller Heater Modules Control", "chillerHeaterModulesControlSchedule", false, "Availability", 0.0, 1.0}, | ||
| {"ChillerElectricASHRAE205", "Ambient Temperature", "ambientTemperatureSchedule", true, "Temperature", OptionalDouble(), OptionalDouble()}, |
There was a problem hiding this comment.
Is availability schedule absorbed into the representation file?
As I said in #4687 (comment) I actually did all the the implementation and testing with heat Recovery Connection enabled (this is the only one that is not supported by E+), and set to be the tertiary loop connection. I disabled it at model time last in 1211428#diff-d88d3c168ac09d7579c9e21a71689ea7bef7c0a1ec822e1c2624d20ac04ed47cR62-R63. That commit can be reverted (or that constexpr changed to true and you're good to go) |
|
I'm trying to kill the V22.2.0-IOFreeze branch sicne you started a different one for 22.2.0 official, so merging now |
Pull request overview
Pull Request Author
src/model/test)src/energyplus/Test)src/osversion/VersionTranslator.cpp)Labels:
IDDChangeAPIChangePull Request - Ready for CIso that CI builds your PRReview Checklist
This will not be exhaustively relevant to every PR.