From 5983e4dca4117711a8ca0aa463d943ae5e600c7b Mon Sep 17 00:00:00 2001 From: Matt Mitchell Date: Mon, 9 Feb 2026 11:46:28 -0700 Subject: [PATCH 1/2] rename SolarHeatingPythonPlugin, move to LINK_WITH_PYTHON --- testfiles/CMakeLists.txt | 8 +- testfiles/SolarHeatingPythonPlugin.idf | 433 ------------------------- testfiles/SolarHeatingPythonPlugin.py | 357 -------------------- 3 files changed, 4 insertions(+), 794 deletions(-) delete mode 100644 testfiles/SolarHeatingPythonPlugin.idf delete mode 100644 testfiles/SolarHeatingPythonPlugin.py diff --git a/testfiles/CMakeLists.txt b/testfiles/CMakeLists.txt index 3ea0b344018..8ea128fe03c 100644 --- a/testfiles/CMakeLists.txt +++ b/testfiles/CMakeLists.txt @@ -490,6 +490,7 @@ add_simulation_test(IDF_FILE PurchAirWithDaylightingAndShadeControl.idf EPW_FILE add_simulation_test(IDF_FILE PurchAirWithDaylightingAngleFac.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE PurchAirWithDoubleFacadeDaylighting.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) if (LINK_WITH_PYTHON) + add_simulation_test(IDF_FILE PythonPlugin_SingleFamilyHouse_TwoSpeed_MultiStageElectricSuppCoil.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE PythonPlugin1ZoneUncontrolledCondFD.idf EPW_FILE USA_CO_Golden-NREL.724666_TMY3.epw) add_simulation_test(IDF_FILE PythonPlugin1ZoneUncontrolledTrackHistory.idf EPW_FILE USA_CO_Golden-NREL.724666_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginAirflowNetworkOpeningControlByHumidity.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) @@ -497,21 +498,21 @@ if (LINK_WITH_PYTHON) add_simulation_test(IDF_FILE PythonPluginCurveOverride_PackagedTerminalHeatPump.idf EPW_FILE USA_FL_Miami.Intl.AP.722020_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginCustomOutputVariable.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginCustomSchedule.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) + add_simulation_test(IDF_FILE PythonPluginCustomTrendVariable.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginDemandManager_LargeOffice.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginDiscreteAirSystemSizes.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginLoadBasedMultiSpeedDXCoilOverrideControl.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) + add_simulation_test(IDF_FILE PythonPluginLrgOff_GridStorageSmoothing.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginMultiSpeedHeatPumpOverrideControl.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) - add_simulation_test(IDF_FILE PythonPlugin_SingleFamilyHouse_TwoSpeed_MultiStageElectricSuppCoil.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginPlantLoopOverrideControl.idf EPW_FILE USA_FL_Orlando.Intl.AP.722050_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginPlantOperation_largeOff.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginReplaceTraditionalManagers_LargeOffice.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) + add_simulation_test(IDF_FILE PythonPluginSolarHeating.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw ANNUAL_SIMULATION) add_simulation_test(IDF_FILE PythonPluginTestMathAndKill.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw EXPECT_FATAL) # Expected to Fatal add_simulation_test(IDF_FILE PythonPluginThermochromicWindow.idf EPW_FILE USA_NV_Las.Vegas-McCarran.Intl.AP.723860_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginUserDefined5ZoneAirCooled.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginUserDefinedWindACAuto.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE PythonPluginWindowShadeControl.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) - add_simulation_test(IDF_FILE PythonPluginLrgOff_GridStorageSmoothing.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) - add_simulation_test(IDF_FILE PythonPluginCustomTrendVariable.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) endif () add_simulation_test(IDF_FILE QTFtest.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE RadHiTempElecTermReheat.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) @@ -598,7 +599,6 @@ add_simulation_test(IDF_FILE SmOffPSZ-MultiModeDX.idf EPW_FILE USA_IL_Chicago-OH add_simulation_test(IDF_FILE SmOffPSZ.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE SmOffPSZ_OnOffStagedControl.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) add_simulation_test(IDF_FILE SolarCollectorFlatPlateWater.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw) -add_simulation_test(IDF_FILE SolarHeatingPythonPlugin.idf EPW_FILE USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.epw ANNUAL_SIMULATION) add_simulation_test(IDF_FILE SolarShadingTest.idf EPW_FILE USA_AZ_Phoenix-Sky.Harbor.Intl.AP.722780_TMY3.epw) add_simulation_test(IDF_FILE SolarShadingTestGPU.idf EPW_FILE USA_AZ_Phoenix-Sky.Harbor.Intl.AP.722780_TMY3.epw) add_simulation_test(IDF_FILE SolarShadingTest_SQL.idf EPW_FILE USA_AZ_Phoenix-Sky.Harbor.Intl.AP.722780_TMY3.epw) diff --git a/testfiles/SolarHeatingPythonPlugin.idf b/testfiles/SolarHeatingPythonPlugin.idf deleted file mode 100644 index 2147eaa03a0..00000000000 --- a/testfiles/SolarHeatingPythonPlugin.idf +++ /dev/null @@ -1,433 +0,0 @@ -!-Generator IDFEditor 1.53 -!-Option SortedOrder -!-NOTE: All comments with '!-' are ignored by the IDFEditor and are generated automatically. -!- Use '!' comments if they need to be retained when using the IDFEditor. -!- =========== ALL OBJECTS IN CLASS: VERSION =========== - - Version,26.1; - -!- =========== ALL OBJECTS IN CLASS: SIMULATIONCONTROL =========== - - SimulationControl, - No, !- Do Zone Sizing Calculation - No, !- Do System Sizing Calculation - No, !- Do Plant Sizing Calculation - No, !- Run Simulation for Sizing Periods - Yes, !- Run Simulation for Weather File Run Periods - , !- Do HVAC Sizing Simulation for Sizing Periods - ; !- Maximum Number of HVAC Sizing Simulation Passes - -!- =========== ALL OBJECTS IN CLASS: BUILDING =========== - - Building, - ThermosolarFacility, !- Name - , !- North Axis {deg} - , !- Terrain - , !- Loads Convergence Tolerance Value {W} - , !- Temperature Convergence Tolerance Value {deltaC} - , !- Solar Distribution - , !- Maximum Number of Warmup Days - ; !- Minimum Number of Warmup Days - -!- =========== ALL OBJECTS IN CLASS: SHADOWCALCULATION =========== - - ShadowCalculation, - PolygonClipping, !- Shading Calculation Method - Periodic, !- Shading Calculation Update Frequency Method - 20, !- Shading Calculation Update Frequency - 15000, !- Maximum Figures in Shadow Overlap Calculations - SutherlandHodgman, !- Polygon Clipping Algorithm - 512, !- Pixel Counting Resolution - SimpleSkyDiffuseModeling,!- Sky Diffuse Modeling Algorithm - No, !- Output External Shading Calculation Results - No, !- Disable Self-Shading Within Shading Zone Groups - No; !- Disable Self-Shading From Shading Zone Groups to Other Zones - -!- =========== ALL OBJECTS IN CLASS: HEATBALANCEALGORITHM =========== - - HeatBalanceAlgorithm,ConductionTransferFunction,200; - -!- =========== ALL OBJECTS IN CLASS: TIMESTEP =========== - - Timestep,6; - -!- =========== ALL OBJECTS IN CLASS: SITE:LOCATION =========== - - Site:Location, - Zacatecas, !- Name - 22.75, !- Latitude {deg} - -102.5, !- Longitude {deg} - -6, !- Time Zone {hr} - 2440; !- Elevation {m} - -!- =========== ALL OBJECTS IN CLASS: RUNPERIOD =========== - - RunPeriod, - Run Period 1, !- Name - 1, !- Begin Month - 1, !- Begin Day of Month - 2024, !- Begin Year - 12, !- End Month - 31, !- End Day of Month - 2024, !- End Year - Monday, !- Day of Week for Start Day - No, !- Use Weather File Holidays and Special Days - No, !- Use Weather File Daylight Saving Period - No, !- Apply Weekend Holiday Rule - Yes, !- Use Weather File Rain Indicators - Yes; !- Use Weather File Snow Indicators - -!- =========== ALL OBJECTS IN CLASS: SCHEDULETYPELIMITS =========== - - ScheduleTypeLimits, - ActivityLevel, !- Name - 0, !- Lower Limit Value - , !- Upper Limit Value - Continuous, !- Numeric Type - activitylevel; !- Unit Type - - ScheduleTypeLimits, - Fraction, !- Name - 0, !- Lower Limit Value - 1, !- Upper Limit Value - CONTINUOUS; !- Numeric Type - -!- =========== ALL OBJECTS IN CLASS: MATERIAL =========== - - Material, - F16 Acoustic tile, !- Name - MediumSmooth, !- Roughness - 0.0191, !- Thickness {m} - 0.06, !- Conductivity {W/m-K} - 368, !- Density {kg/m3} - 590.000000000002, !- Specific Heat {J/kg-K} - 0.9, !- Thermal Absorptance - 0.3, !- Solar Absorptance - 0.3; !- Visible Absorptance - - Material, - G01a 19mm gypsum board, !- Name - MediumSmooth, !- Roughness - 0.019, !- Thickness {m} - 0.16, !- Conductivity {W/m-K} - 800, !- Density {kg/m3} - 1090, !- Specific Heat {J/kg-K} - 0.9, !- Thermal Absorptance - 0.4, !- Solar Absorptance - 0.4; !- Visible Absorptance - - Material, - M11 100mm lightweight concrete, !- Name - MediumRough, !- Roughness - 0.1016, !- Thickness {m} - 0.53, !- Conductivity {W/m-K} - 1280, !- Density {kg/m3} - 840.000000000002, !- Specific Heat {J/kg-K} - 0.9, !- Thermal Absorptance - 0.5, !- Solar Absorptance - 0.5; !- Visible Absorptance - -!- =========== ALL OBJECTS IN CLASS: MATERIAL:AIRGAP =========== - - Material:AirGap, - F04 Wall air space resistance, !- Name - 0.15; !- Thermal Resistance {m2-K/W} - - Material:AirGap, - F05 Ceiling air space resistance, !- Name - 0.18; !- Thermal Resistance {m2-K/W} - -!- =========== ALL OBJECTS IN CLASS: CONSTRUCTION =========== - - Construction, - Interior Ceiling, !- Name - M11 100mm lightweight concrete, !- Outside Layer - F05 Ceiling air space resistance, !- Layer 2 - F16 Acoustic tile; !- Layer 3 - - Construction, - Interior Floor, !- Name - F16 Acoustic tile, !- Outside Layer - F05 Ceiling air space resistance, !- Layer 2 - M11 100mm lightweight concrete; !- Layer 3 - - Construction, - Interior Wall, !- Name - G01a 19mm gypsum board, !- Outside Layer - F04 Wall air space resistance, !- Layer 2 - G01a 19mm gypsum board; !- Layer 3 - -!- =========== ALL OBJECTS IN CLASS: GLOBALGEOMETRYRULES =========== - - GlobalGeometryRules, - UpperLeftCorner, !- Starting Vertex Position - Counterclockwise, !- Vertex Entry Direction - Relative, !- Coordinate System - Relative, !- Daylighting Reference Point Coordinate System - Relative; !- Rectangular Surface Coordinate System - -!- =========== ALL OBJECTS IN CLASS: SPACE =========== - - Space, - space, !- Name - ThermalZone, !- Zone Name - , !- Ceiling Height {m} - , !- Volume {m3} - , !- Floor Area {m2} - ; !- Space Type - -!- =========== ALL OBJECTS IN CLASS: ZONE =========== - - Zone, - ThermalZone, !- Name - , !- Direction of Relative North {deg} - 0, !- X Origin {m} - 0, !- Y Origin {m} - 0, !- Z Origin {m} - , !- Type - 1, !- Multiplier - , !- Ceiling Height {m} - , !- Volume {m3} - , !- Floor Area {m2} - , !- Zone Inside Convection Algorithm - , !- Zone Outside Convection Algorithm - Yes; !- Part of Total Floor Area - -!- =========== ALL OBJECTS IN CLASS: BUILDINGSURFACE:DETAILED =========== - - BuildingSurface:Detailed, - space_floor, !- Name - Floor, !- Surface Type - Interior Floor, !- Construction Name - ThermalZone, !- Zone Name - space, !- Space Name - Adiabatic, !- Outside Boundary Condition - , !- Outside Boundary Condition Object - NoSun, !- Sun Exposure - NoWind, !- Wind Exposure - , !- View Factor to Ground - , !- Number of Vertices - 0,0,0, !- X,Y,Z ==> Vertex 1 {m} - 0,1,0, !- X,Y,Z ==> Vertex 2 {m} - 1,1,0, !- X,Y,Z ==> Vertex 3 {m} - 1,0,0; !- X,Y,Z ==> Vertex 4 {m} - - BuildingSurface:Detailed, - space_roofceiling, !- Name - Ceiling, !- Surface Type - Interior Ceiling, !- Construction Name - ThermalZone, !- Zone Name - space, !- Space Name - Adiabatic, !- Outside Boundary Condition - , !- Outside Boundary Condition Object - NoSun, !- Sun Exposure - NoWind, !- Wind Exposure - , !- View Factor to Ground - , !- Number of Vertices - 1,0,1, !- X,Y,Z ==> Vertex 1 {m} - 1,1,1, !- X,Y,Z ==> Vertex 2 {m} - 0,1,1, !- X,Y,Z ==> Vertex 3 {m} - 0,0,1; !- X,Y,Z ==> Vertex 4 {m} - - BuildingSurface:Detailed, - space_wall0, !- Name - Wall, !- Surface Type - Interior Wall, !- Construction Name - ThermalZone, !- Zone Name - space, !- Space Name - Adiabatic, !- Outside Boundary Condition - , !- Outside Boundary Condition Object - NoSun, !- Sun Exposure - NoWind, !- Wind Exposure - , !- View Factor to Ground - , !- Number of Vertices - 0,0,1, !- X,Y,Z ==> Vertex 1 {m} - 0,1,1, !- X,Y,Z ==> Vertex 2 {m} - 0,1,0, !- X,Y,Z ==> Vertex 3 {m} - 0,0,0; !- X,Y,Z ==> Vertex 4 {m} - - BuildingSurface:Detailed, - space_wall1, !- Name - Wall, !- Surface Type - Interior Wall, !- Construction Name - ThermalZone, !- Zone Name - space, !- Space Name - Adiabatic, !- Outside Boundary Condition - , !- Outside Boundary Condition Object - NoSun, !- Sun Exposure - NoWind, !- Wind Exposure - , !- View Factor to Ground - , !- Number of Vertices - 0,1,1, !- X,Y,Z ==> Vertex 1 {m} - 1,1,1, !- X,Y,Z ==> Vertex 2 {m} - 1,1,0, !- X,Y,Z ==> Vertex 3 {m} - 0,1,0; !- X,Y,Z ==> Vertex 4 {m} - - BuildingSurface:Detailed, - space_wall2, !- Name - Wall, !- Surface Type - Interior Wall, !- Construction Name - ThermalZone, !- Zone Name - space, !- Space Name - Adiabatic, !- Outside Boundary Condition - , !- Outside Boundary Condition Object - NoSun, !- Sun Exposure - NoWind, !- Wind Exposure - , !- View Factor to Ground - , !- Number of Vertices - 1,0,1, !- X,Y,Z ==> Vertex 1 {m} - 0,0,1, !- X,Y,Z ==> Vertex 2 {m} - 0,0,0, !- X,Y,Z ==> Vertex 3 {m} - 1,0,0; !- X,Y,Z ==> Vertex 4 {m} - - BuildingSurface:Detailed, - space_wall3, !- Name - Wall, !- Surface Type - Interior Wall, !- Construction Name - ThermalZone, !- Zone Name - space, !- Space Name - Adiabatic, !- Outside Boundary Condition - , !- Outside Boundary Condition Object - NoSun, !- Sun Exposure - NoWind, !- Wind Exposure - , !- View Factor to Ground - , !- Number of Vertices - 1,1,1, !- X,Y,Z ==> Vertex 1 {m} - 1,0,1, !- X,Y,Z ==> Vertex 2 {m} - 1,0,0, !- X,Y,Z ==> Vertex 3 {m} - 1,1,0; !- X,Y,Z ==> Vertex 4 {m} - -!- =========== ALL OBJECTS IN CLASS: ZONEHVAC:AIRDISTRIBUTIONUNIT =========== - - ZoneHVAC:AirDistributionUnit, - TunnelTerminal, !- Name - TunnelInlet, !- Air Distribution Unit Outlet Node Name - AirTerminal:SingleDuct:UserDefined, !- Air Terminal Object Type - AirMixer; !- Air Terminal Name - -!- =========== ALL OBJECTS IN CLASS: ZONEHVAC:EQUIPMENTLIST =========== - - ZoneHVAC:EquipmentList, - SolarHeatingSystem, !- Name - SequentialLoad, !- Load Distribution Scheme - ZoneHVAC:AirDistributionUnit, !- Zone Equipment 1 Object Type - TunnelTerminal, !- Zone Equipment 1 Name - 3, !- Zone Equipment 1 Cooling Sequence - 3, !- Zone Equipment 1 Heating or No-Load Sequence - , !- Zone Equipment 1 Sequential Cooling Fraction Schedule Name - , !- Zone Equipment 1 Sequential Heating Fraction Schedule Name - ZoneHVAC:ForcedAir:UserDefined, !- Zone Equipment 2 Object Type - IndirectSolarAirHeater, !- Zone Equipment 2 Name - 2, !- Zone Equipment 2 Cooling Sequence - 2, !- Zone Equipment 2 Heating or No-Load Sequence - , !- Zone Equipment 2 Sequential Cooling Fraction Schedule Name - , !- Zone Equipment 2 Sequential Heating Fraction Schedule Name - ZoneHVAC:ForcedAir:UserDefined, !- Zone Equipment 3 Object Type - DirectSolarAirHeater, !- Zone Equipment 3 Name - 1, !- Zone Equipment 3 Cooling Sequence - 1; !- Zone Equipment 3 Heating or No-Load Sequence - -!- =========== ALL OBJECTS IN CLASS: ZONEHVAC:EQUIPMENTCONNECTIONS =========== - - ZoneHVAC:EquipmentConnections, - ThermalZone, !- Zone Name - SolarHeatingSystem, !- Zone Conditioning Equipment List Name - TunnelInlet, !- Zone Air Inlet Node or NodeList Name - , !- Zone Air Exhaust Node or NodeList Name - TunnelZoneNode; !- Zone Air Node Name - -!- =========== ALL OBJECTS IN CLASS: OUTDOORAIR:NODE =========== - - OutdoorAir:Node, - OA_DSAH_Node; !- Name - - OutdoorAir:Node, - OA_ISAH_Node; !- Name - -!- =========== ALL OBJECTS IN CLASS: ZONEHVAC:FORCEDAIR:USERDEFINED =========== - - ZoneHVAC:ForcedAir:UserDefined, - DirectSolarAirHeater, !- Name - DirectSolarHeatingEMS, !- Overall Model Simulation Program Calling Manager Name - , !- Model Setup and Sizing Program Calling Manager Name - OA_DSAH_Node, !- Primary Air Inlet Node Name - DSAHOutletNode, !- Primary Air Outlet Node Name - , !- Secondary Air Inlet Node Name - , !- Secondary Air Outlet Node Name - 0; !- Number of Plant Loop Connections - - ZoneHVAC:ForcedAir:UserDefined, - IndirectSolarAirHeater, !- Name - IndirectSolarHeatingEMS, !- Overall Model Simulation Program Calling Manager Name - , !- Model Setup and Sizing Program Calling Manager Name - OA_ISAH_Node, !- Primary Air Inlet Node Name - ISAHOutletNode, !- Primary Air Outlet Node Name - , !- Secondary Air Inlet Node Name - , !- Secondary Air Outlet Node Name - 0; !- Number of Plant Loop Connections - -!- =========== ALL OBJECTS IN CLASS: AIRTERMINAL:SINGLEDUCT:USERDEFINED =========== - - AirTerminal:SingleDuct:UserDefined, - AirMixer, !- Name - AirMixingEMS, !- Overall Model Simulation Program Calling Manager Name - , !- Model Setup and Sizing Program Calling Manager Name - DSAHOutletNode, !- Primary Air Inlet Node Name - TunnelInlet, !- Primary Air Outlet Node Name - ISAHOutletNode, !- Secondary Air Inlet Node Name - , !- Secondary Air Outlet Node Name - 0, !- Number of Plant Loop Connections - AMXRPlantInletNode, !- Plant Connection 1 Inlet Node Name - AMXRPlantOutletNode; !- Plant Connection 1 Outlet Node Name - -!- =========== ALL OBJECTS IN CLASS: OUTPUT:VARIABLEDICTIONARY =========== - - Output:VariableDictionary,IDF,Unsorted; - -!- =========== ALL OBJECTS IN CLASS: OUTPUT:TABLE:SUMMARYREPORTS =========== - - Output:Table:SummaryReports, - AllSummary; !- Report 1 Name - -!- =========== ALL OBJECTS IN CLASS: OUTPUTCONTROL:TABLE:STYLE =========== - - OutputControl:Table:Style, - HTML; !- Column Separator - -!- =========== ALL OBJECTS IN CLASS: OUTPUT:VARIABLE =========== - - Output:Variable,*,Site Outdoor Air Drybulb Temperature,Timestep; - - Output:Variable,*,Site Outdoor Air Humidity Ratio,Timestep; - - Output:Variable,*,System Node Temperature,Timestep; - - Output:Variable,*,System Node Humidity Ratio,Timestep; - - Output:Variable,*,System Node Mass Flow Rate,Timestep; - -!- =========== ALL OBJECTS IN CLASS: OUTPUT:SQLITE =========== - - Output:SQLite, - SimpleAndTabular; !- Option Type - -!- =========== ALL OBJECTS IN CLASS: PYTHONPLUGIN:INSTANCE =========== - - PythonPlugin:Instance, - DirectSolarHeatingEMS, !- Name - No, !- Run During Warmup Days - SolarHeatingPythonPlugin,!- Python Module Name - DirectSolarHeating; !- Plugin Class Name - - PythonPlugin:Instance, - IndirectSolarHeatingEMS, !- Name - No, !- Run During Warmup Days - SolarHeatingPythonPlugin,!- Python Module Name - IndirectSolarHeating; !- Plugin Class Name - - PythonPlugin:Instance, - AirMixingEMS, !- Name - No, !- Run During Warmup Days - SolarHeatingPythonPlugin,!- Python Module Name - AirMixing; !- Plugin Class Name - diff --git a/testfiles/SolarHeatingPythonPlugin.py b/testfiles/SolarHeatingPythonPlugin.py deleted file mode 100644 index ab75a864e4c..00000000000 --- a/testfiles/SolarHeatingPythonPlugin.py +++ /dev/null @@ -1,357 +0,0 @@ -# EnergyPlus, Copyright (c) 1996-2026, The Board of Trustees of the University -# of Illinois, The Regents of the University of California, through Lawrence -# Berkeley National Laboratory (subject to receipt of any required approvals -# from the U.S. Dept. of Energy), Oak Ridge National Laboratory, managed by UT- -# Battelle, Alliance for Energy Innovation, LLC, and other contributors. All -# rights reserved. -# -# NOTICE: This Software was developed under funding from the U.S. Department of -# Energy and the U.S. Government consequently retains certain rights. As such, -# the U.S. Government has been granted for itself and others acting on its -# behalf a paid-up, nonexclusive, irrevocable, worldwide license in the -# Software to reproduce, distribute copies to the public, prepare derivative -# works, and perform publicly and display publicly, and to permit others to do -# so. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# (1) Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# (2) Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# (3) Neither the name of the University of California, Lawrence Berkeley -# National Laboratory, the University of Illinois, U.S. Dept. of Energy nor -# the names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in -# stand-alone form without changes from the version obtained under this -# License, or (ii) Licensee makes a reference solely to the software -# portion of its product, Licensee must refer to the software as -# "EnergyPlus version X" software, where "X" is the version number Licensee -# obtained under this License and may not use a different name for the -# software. Except as specifically required in this Section (4), Licensee -# shall not use in a company name, a product name, in advertising, -# publicity, or other promotional activities any name, trade name, -# trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or -# confusingly similar designation, without the U.S. Department of Energy's -# prior written consent. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -import math -import sys - -from pyenergyplus.plugin import EnergyPlusPlugin - - -class DirectSolarHeating(EnergyPlusPlugin): - - def __init__(self): - # init parent class - super().__init__() - - # members - self.DSAH_MdotIn = 0.0 - self.DSAH_MdotOut = 0.0 - self.DSAH_Tinlet = 0.0 - self.DSAH_Winlet = 0.0 - self.DSAH_Toutlet = 0.0 - self.DSAH_Woutlet = 0.0 - - # handles - self.need_to_get_handles = True - self.handles = {} - - # psych api instance - self.psych = None - - def get_handles(self, state): - self.handles["DSAH_Tinlet"] = self.api.exchange.get_internal_variable_handle( - state, "Inlet Temperature for Primary Air Connection", "DirectSolarAirHeater" - ) - - self.handles["DSAH_Winlet"] = self.api.exchange.get_internal_variable_handle( - state, "Inlet Humidity Ratio for Primary Air Connection", "DirectSolarAirHeater" - ) - - self.handles["DSAH_MdotOut"] = self.api.exchange.get_actuator_handle( - state, "Primary Air Connection", "Outlet Mass Flow Rate", "DirectSolarAirHeater" - ) - self.handles["DSAH_MdotIn"] = self.api.exchange.get_actuator_handle( - state, "Primary Air Connection", "Inlet Mass Flow Rate", "DirectSolarAirHeater" - ) - self.handles["DSAH_Toutlet"] = self.api.exchange.get_actuator_handle( - state, "Primary Air Connection", "Outlet Temperature", "DirectSolarAirHeater" - ) - self.handles["DSAH_Woutlet"] = self.api.exchange.get_actuator_handle( - state, "Primary Air Connection", "Outlet Humidity Ratio", "DirectSolarAirHeater" - ) - self.need_to_get_handles = False - - def handles_gotten_properly(self, state): - handles_ok = True - - for k, v in self.handles.items(): - if v == -1: - handles_ok = False - self.api.runtime.issue_severe(state, f"Handle not found for '{k}'") - - return handles_ok - - def initialize(self, state): - self.DSAH_Tinlet = self.api.exchange.get_internal_variable_value(state, self.handles["DSAH_Tinlet"]) - self.DSAH_Winlet = self.api.exchange.get_internal_variable_value(state, self.handles["DSAH_Winlet"]) - - def simulate(self, state): - # First simulation, assumed a predefined Heating Capacity, the DSAH will heat the air - # according to it - HCapacity = 3000 # W - QDesign = 1.0 # m^3/s - Patm = 77700 # Pa - VspIn = self.psych.specific_volume(state, self.DSAH_Tinlet, self.DSAH_Winlet, Patm) - self.DSAH_MdotIn = QDesign / VspIn - self.DSAH_MdotOut = self.DSAH_MdotIn - hInlet = self.psych.enthalpy(state, self.DSAH_Tinlet, self.DSAH_Winlet) - hOutlet = hInlet + HCapacity / self.DSAH_MdotOut - self.DSAH_Toutlet = self.psych.dry_bulb(state, hOutlet, self.DSAH_Winlet) - self.DSAH_Woutlet = self.DSAH_Winlet - - def report(self, state): - self.api.exchange.set_actuator_value(state, self.handles["DSAH_MdotOut"], self.DSAH_MdotOut) - self.api.exchange.set_actuator_value(state, self.handles["DSAH_MdotIn"], self.DSAH_MdotIn) - self.api.exchange.set_actuator_value(state, self.handles["DSAH_Toutlet"], self.DSAH_Toutlet) - self.api.exchange.set_actuator_value(state, self.handles["DSAH_Woutlet"], self.DSAH_Woutlet) - - def on_user_defined_component_model(self, state) -> int: - if not self.psych: - self.psych = self.api.functional.psychrometrics(state) - if self.need_to_get_handles: - self.get_handles(state) - if not self.handles_gotten_properly(state): - return 1 - self.initialize(state) - self.simulate(state) - self.report(state) - return 0 - - -class IndirectSolarHeating(EnergyPlusPlugin): - - def __init__(self): - # init parent class - super().__init__() - - # members - self.ISAH_MdotIn = 0.0 - self.ISAH_MdotOut = 0.0 - self.ISAH_Tinlet = 0.0 - self.ISAH_Winlet = 0.0 - self.ISAH_Toutlet = 0.0 - self.ISAH_Woutlet = 0.0 - - # handles - self.need_to_get_handles = True - self.handles = {} - - # psych api instance - self.psych = None - - def get_handles(self, state): - self.handles["ISAH_Tinlet"] = self.api.exchange.get_internal_variable_handle( - state, "Inlet Temperature for Primary Air Connection", "IndirectSolarAirHeater" - ) - - self.handles["ISAH_Winlet"] = self.api.exchange.get_internal_variable_handle( - state, "Inlet Humidity Ratio for Primary Air Connection", "IndirectSolarAirHeater" - ) - - self.handles["ISAH_MdotOut"] = self.api.exchange.get_actuator_handle( - state, "Primary Air Connection", "Outlet Mass Flow Rate", "IndirectSolarAirHeater" - ) - self.handles["ISAH_MdotIn"] = self.api.exchange.get_actuator_handle( - state, "Primary Air Connection", "Inlet Mass Flow Rate", "IndirectSolarAirHeater" - ) - self.handles["ISAH_Toutlet"] = self.api.exchange.get_actuator_handle( - state, "Primary Air Connection", "Outlet Temperature", "IndirectSolarAirHeater" - ) - self.handles["ISAH_Woutlet"] = self.api.exchange.get_actuator_handle( - state, "Primary Air Connection", "Outlet Humidity Ratio", "IndirectSolarAirHeater" - ) - self.need_to_get_handles = False - - def handles_gotten_properly(self, state): - handles_ok = True - - for k, v in self.handles.items(): - if v == -1: - handles_ok = False - self.api.runtime.issue_severe(state, f"Handle not found for '{k}'") - - return handles_ok - - def initialize(self, state): - self.ISAH_Tinlet = self.api.exchange.get_internal_variable_value(state, self.handles["ISAH_Tinlet"]) - self.ISAH_Winlet = self.api.exchange.get_internal_variable_value(state, self.handles["ISAH_Winlet"]) - - def simulate(self, state): - # First simulation, assumed a predefined Evaporative Cooling Effectiveness of 0.6 - QDesign = 2.0 # m^3/s - Patm = 77700 # Pa - VspIn = self.psych.specific_volume(state, self.ISAH_Tinlet, self.ISAH_Winlet, Patm) - self.ISAH_MdotIn = QDesign / VspIn - self.ISAH_MdotOut = self.ISAH_MdotIn - Twb = self.psych.wet_bulb(state, self.ISAH_Tinlet, self.ISAH_Winlet, Patm) - self.ISAH_Toutlet = 0.6 * (self.ISAH_Tinlet - Twb) - self.ISAH_Woutlet = self.psych.humidity_ratio_d(state, self.ISAH_Tinlet, Twb, Patm) - - def report(self, state): - self.api.exchange.set_actuator_value(state, self.handles["ISAH_MdotOut"], self.ISAH_MdotOut) - self.api.exchange.set_actuator_value(state, self.handles["ISAH_MdotIn"], self.ISAH_MdotIn) - self.api.exchange.set_actuator_value(state, self.handles["ISAH_Toutlet"], self.ISAH_Toutlet) - self.api.exchange.set_actuator_value(state, self.handles["ISAH_Woutlet"], self.ISAH_Woutlet) - - def on_user_defined_component_model(self, state) -> int: - if not self.psych: - self.psych = self.api.functional.psychrometrics(state) - if self.need_to_get_handles: - self.get_handles(state) - if not self.handles_gotten_properly(state): - return 1 - self.initialize(state) - self.simulate(state) - self.report(state) - return 0 - - -class AirMixing(EnergyPlusPlugin): - - def __init__(self): - # init parent class - super().__init__() - - # members - self.AMXR_MdotIn1 = 0.0 - self.AMXR_MdotIn2 = 0.0 - self.AMXR_MdotOut = 0.0 - self.AMXR_Tinlet1 = 0.0 - self.AMXR_Winlet1 = 0.0 - self.AMXR_Tinlet2 = 0.0 - self.AMXR_Winlet2 = 0.0 - self.AMXR_Toutlet = 0.0 - self.AMXR_Woutlet = 0.0 - - # handles - self.need_to_get_handles = True - self.handles = {} - - # psych api instance - self.psych = None - - def get_handles(self, state): - self.handles["AMXR_MdotIn1"] = self.api.exchange.get_variable_handle( - state, "System Node Mass Flow Rate", "DSAHOutletNode" - ) - # - self.handles["AMXR_MdotIn2"] = self.api.exchange.get_variable_handle( - state, "System Node Mass Flow Rate", "ISAHOutletNode" - ) - - self.handles["AMXR_Tinlet1"] = self.api.exchange.get_internal_variable_handle( - state, "Inlet Temperature for Primary Air Connection", "AirMixer" - ) - - self.handles["AMXR_Winlet1"] = self.api.exchange.get_internal_variable_handle( - state, "Inlet Humidity Ratio for Primary Air Connection", "AirMixer" - ) - - self.handles["AMXR_Tinlet2"] = self.api.exchange.get_internal_variable_handle( - state, "Inlet Temperature for Secondary Air Connection", "AirMixer" - ) - - self.handles["AMXR_Winlet2"] = self.api.exchange.get_internal_variable_handle( - state, "Inlet Humidity Ratio for Secondary Air Connection", "AirMixer" - ) - - self.handles["AMXR_MdotOut"] = self.api.exchange.get_actuator_handle( - state, "Primary Air Connection", "Outlet Mass Flow Rate", "AirMixer" - ) - - self.handles["AMXR_Toutlet"] = self.api.exchange.get_actuator_handle( - state, "Primary Air Connection", "Outlet Temperature", "AirMixer" - ) - - self.handles["AMXR_Woutlet"] = self.api.exchange.get_actuator_handle( - state, "Primary Air Connection", "Outlet Humidity Ratio", "AirMixer" - ) - - self.need_to_get_handles = False - - def handles_gotten_properly(self, state): - handles_ok = True - - for k, v in self.handles.items(): - if v == -1: - handles_ok = False - self.api.runtime.issue_severe(state, f"Handle not found for '{k}'") - - return handles_ok - - def initialize(self, state): - self.AMXR_Tinlet1 = self.api.exchange.get_internal_variable_value(state, self.handles["AMXR_Tinlet1"]) - self.AMXR_Winlet1 = self.api.exchange.get_internal_variable_value(state, self.handles["AMXR_Winlet1"]) - self.AMXR_MdotIn1 = self.api.exchange.get_variable_value(state, self.handles["AMXR_MdotIn1"]) - self.AMXR_Tinlet2 = self.api.exchange.get_internal_variable_value(state, self.handles["AMXR_Tinlet2"]) - self.AMXR_Winlet2 = self.api.exchange.get_internal_variable_value(state, self.handles["AMXR_Winlet1"]) - self.AMXR_MdotIn2 = self.api.exchange.get_variable_value(state, self.handles["AMXR_MdotIn2"]) - - def simulate(self, state): - # Second simulation, the function will find output conditions of a mixed air - self.AMXR_MdotOut = self.AMXR_MdotIn1 + self.AMXR_MdotIn2 - try: - rmix = self.AMXR_MdotIn2 / self.AMXR_MdotIn1 - h1 = self.psych.enthalpy(self.AMXR_Tinlet1, self.AMXR_Winlet1) - h2 = self.psych.enthalpy(self.AMXR_Tinlet2, self.AMXR_Winlet2) - h3 = h1 + rmix * (h2 - h1) - self.AMXR_Woutlet = self.AMXR_Winlet1 + rmix * (self.AMXR_Winlet2 - self.AMXR_Winlet1) - self.AMXR_Toutlet = self.psych.dry_bulb(h3, self.AMXR_Woutlet) - except ZeroDivisionError as e: - if self.AMXR_MdotIn1 == 0.0 and self.AMXR_MdotIn2 == 0.0: - self.AMXR_MdotOut = 0.0 - self.AMXR_Woutlet = (self.AMXR_Winlet1 + self.AMXR_Winlet2) / 2.0 - self.AMXR_Toutlet = (self.AMXR_Tinlet1 + self.AMXR_Tinlet2) / 2.0 - else: - self.AMXR_MdotOut = self.AMXR_MdotIn2 - self.AMXR_Woutlet = self.AMXR_Winlet2 - self.AMXR_Toutlet = self.AMXR_Tinlet2 - - def report(self, state): - self.api.exchange.set_actuator_value(state, self.handles["AMXR_MdotOut"], self.AMXR_MdotOut) - self.api.exchange.set_actuator_value(state, self.handles["AMXR_Toutlet"], self.AMXR_Toutlet) - self.api.exchange.set_actuator_value(state, self.handles["AMXR_Woutlet"], self.AMXR_Woutlet) - - def on_begin_zone_timestep_before_init_heat_balance(self, state) -> int: - if not self.psych: - self.psych = self.api.functional.psychrometrics(state) - if self.need_to_get_handles: - self.get_handles(state) - if not self.handles_gotten_properly(state): - return 1 - self.initialize(state) - self.simulate(state) - self.report(state) - return 0 From 70dc30ff040c14c6261b77a1b5b0de475e1166d6 Mon Sep 17 00:00:00 2001 From: Matt Mitchell Date: Mon, 9 Feb 2026 12:03:39 -0700 Subject: [PATCH 2/2] add files after rename --- testfiles/PythonPluginSolarHeating.idf | 433 +++++++++++++++++++++++++ testfiles/PythonPluginSolarHeating.py | 357 ++++++++++++++++++++ 2 files changed, 790 insertions(+) create mode 100644 testfiles/PythonPluginSolarHeating.idf create mode 100644 testfiles/PythonPluginSolarHeating.py diff --git a/testfiles/PythonPluginSolarHeating.idf b/testfiles/PythonPluginSolarHeating.idf new file mode 100644 index 00000000000..205b014c80c --- /dev/null +++ b/testfiles/PythonPluginSolarHeating.idf @@ -0,0 +1,433 @@ +!-Generator IDFEditor 1.53 +!-Option SortedOrder +!-NOTE: All comments with '!-' are ignored by the IDFEditor and are generated automatically. +!- Use '!' comments if they need to be retained when using the IDFEditor. +!- =========== ALL OBJECTS IN CLASS: VERSION =========== + + Version,26.1; + +!- =========== ALL OBJECTS IN CLASS: SIMULATIONCONTROL =========== + + SimulationControl, + No, !- Do Zone Sizing Calculation + No, !- Do System Sizing Calculation + No, !- Do Plant Sizing Calculation + No, !- Run Simulation for Sizing Periods + Yes, !- Run Simulation for Weather File Run Periods + , !- Do HVAC Sizing Simulation for Sizing Periods + ; !- Maximum Number of HVAC Sizing Simulation Passes + +!- =========== ALL OBJECTS IN CLASS: BUILDING =========== + + Building, + ThermosolarFacility, !- Name + , !- North Axis {deg} + , !- Terrain + , !- Loads Convergence Tolerance Value {W} + , !- Temperature Convergence Tolerance Value {deltaC} + , !- Solar Distribution + , !- Maximum Number of Warmup Days + ; !- Minimum Number of Warmup Days + +!- =========== ALL OBJECTS IN CLASS: SHADOWCALCULATION =========== + + ShadowCalculation, + PolygonClipping, !- Shading Calculation Method + Periodic, !- Shading Calculation Update Frequency Method + 20, !- Shading Calculation Update Frequency + 15000, !- Maximum Figures in Shadow Overlap Calculations + SutherlandHodgman, !- Polygon Clipping Algorithm + 512, !- Pixel Counting Resolution + SimpleSkyDiffuseModeling,!- Sky Diffuse Modeling Algorithm + No, !- Output External Shading Calculation Results + No, !- Disable Self-Shading Within Shading Zone Groups + No; !- Disable Self-Shading From Shading Zone Groups to Other Zones + +!- =========== ALL OBJECTS IN CLASS: HEATBALANCEALGORITHM =========== + + HeatBalanceAlgorithm,ConductionTransferFunction,200; + +!- =========== ALL OBJECTS IN CLASS: TIMESTEP =========== + + Timestep,6; + +!- =========== ALL OBJECTS IN CLASS: SITE:LOCATION =========== + + Site:Location, + Zacatecas, !- Name + 22.75, !- Latitude {deg} + -102.5, !- Longitude {deg} + -6, !- Time Zone {hr} + 2440; !- Elevation {m} + +!- =========== ALL OBJECTS IN CLASS: RUNPERIOD =========== + + RunPeriod, + Run Period 1, !- Name + 1, !- Begin Month + 1, !- Begin Day of Month + 2024, !- Begin Year + 12, !- End Month + 31, !- End Day of Month + 2024, !- End Year + Monday, !- Day of Week for Start Day + No, !- Use Weather File Holidays and Special Days + No, !- Use Weather File Daylight Saving Period + No, !- Apply Weekend Holiday Rule + Yes, !- Use Weather File Rain Indicators + Yes; !- Use Weather File Snow Indicators + +!- =========== ALL OBJECTS IN CLASS: SCHEDULETYPELIMITS =========== + + ScheduleTypeLimits, + ActivityLevel, !- Name + 0, !- Lower Limit Value + , !- Upper Limit Value + Continuous, !- Numeric Type + activitylevel; !- Unit Type + + ScheduleTypeLimits, + Fraction, !- Name + 0, !- Lower Limit Value + 1, !- Upper Limit Value + CONTINUOUS; !- Numeric Type + +!- =========== ALL OBJECTS IN CLASS: MATERIAL =========== + + Material, + F16 Acoustic tile, !- Name + MediumSmooth, !- Roughness + 0.0191, !- Thickness {m} + 0.06, !- Conductivity {W/m-K} + 368, !- Density {kg/m3} + 590.000000000002, !- Specific Heat {J/kg-K} + 0.9, !- Thermal Absorptance + 0.3, !- Solar Absorptance + 0.3; !- Visible Absorptance + + Material, + G01a 19mm gypsum board, !- Name + MediumSmooth, !- Roughness + 0.019, !- Thickness {m} + 0.16, !- Conductivity {W/m-K} + 800, !- Density {kg/m3} + 1090, !- Specific Heat {J/kg-K} + 0.9, !- Thermal Absorptance + 0.4, !- Solar Absorptance + 0.4; !- Visible Absorptance + + Material, + M11 100mm lightweight concrete, !- Name + MediumRough, !- Roughness + 0.1016, !- Thickness {m} + 0.53, !- Conductivity {W/m-K} + 1280, !- Density {kg/m3} + 840.000000000002, !- Specific Heat {J/kg-K} + 0.9, !- Thermal Absorptance + 0.5, !- Solar Absorptance + 0.5; !- Visible Absorptance + +!- =========== ALL OBJECTS IN CLASS: MATERIAL:AIRGAP =========== + + Material:AirGap, + F04 Wall air space resistance, !- Name + 0.15; !- Thermal Resistance {m2-K/W} + + Material:AirGap, + F05 Ceiling air space resistance, !- Name + 0.18; !- Thermal Resistance {m2-K/W} + +!- =========== ALL OBJECTS IN CLASS: CONSTRUCTION =========== + + Construction, + Interior Ceiling, !- Name + M11 100mm lightweight concrete, !- Outside Layer + F05 Ceiling air space resistance, !- Layer 2 + F16 Acoustic tile; !- Layer 3 + + Construction, + Interior Floor, !- Name + F16 Acoustic tile, !- Outside Layer + F05 Ceiling air space resistance, !- Layer 2 + M11 100mm lightweight concrete; !- Layer 3 + + Construction, + Interior Wall, !- Name + G01a 19mm gypsum board, !- Outside Layer + F04 Wall air space resistance, !- Layer 2 + G01a 19mm gypsum board; !- Layer 3 + +!- =========== ALL OBJECTS IN CLASS: GLOBALGEOMETRYRULES =========== + + GlobalGeometryRules, + UpperLeftCorner, !- Starting Vertex Position + Counterclockwise, !- Vertex Entry Direction + Relative, !- Coordinate System + Relative, !- Daylighting Reference Point Coordinate System + Relative; !- Rectangular Surface Coordinate System + +!- =========== ALL OBJECTS IN CLASS: SPACE =========== + + Space, + space, !- Name + ThermalZone, !- Zone Name + , !- Ceiling Height {m} + , !- Volume {m3} + , !- Floor Area {m2} + ; !- Space Type + +!- =========== ALL OBJECTS IN CLASS: ZONE =========== + + Zone, + ThermalZone, !- Name + , !- Direction of Relative North {deg} + 0, !- X Origin {m} + 0, !- Y Origin {m} + 0, !- Z Origin {m} + , !- Type + 1, !- Multiplier + , !- Ceiling Height {m} + , !- Volume {m3} + , !- Floor Area {m2} + , !- Zone Inside Convection Algorithm + , !- Zone Outside Convection Algorithm + Yes; !- Part of Total Floor Area + +!- =========== ALL OBJECTS IN CLASS: BUILDINGSURFACE:DETAILED =========== + + BuildingSurface:Detailed, + space_floor, !- Name + Floor, !- Surface Type + Interior Floor, !- Construction Name + ThermalZone, !- Zone Name + space, !- Space Name + Adiabatic, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 0,0,0, !- X,Y,Z ==> Vertex 1 {m} + 0,1,0, !- X,Y,Z ==> Vertex 2 {m} + 1,1,0, !- X,Y,Z ==> Vertex 3 {m} + 1,0,0; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + space_roofceiling, !- Name + Ceiling, !- Surface Type + Interior Ceiling, !- Construction Name + ThermalZone, !- Zone Name + space, !- Space Name + Adiabatic, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 1,0,1, !- X,Y,Z ==> Vertex 1 {m} + 1,1,1, !- X,Y,Z ==> Vertex 2 {m} + 0,1,1, !- X,Y,Z ==> Vertex 3 {m} + 0,0,1; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + space_wall0, !- Name + Wall, !- Surface Type + Interior Wall, !- Construction Name + ThermalZone, !- Zone Name + space, !- Space Name + Adiabatic, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 0,0,1, !- X,Y,Z ==> Vertex 1 {m} + 0,1,1, !- X,Y,Z ==> Vertex 2 {m} + 0,1,0, !- X,Y,Z ==> Vertex 3 {m} + 0,0,0; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + space_wall1, !- Name + Wall, !- Surface Type + Interior Wall, !- Construction Name + ThermalZone, !- Zone Name + space, !- Space Name + Adiabatic, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 0,1,1, !- X,Y,Z ==> Vertex 1 {m} + 1,1,1, !- X,Y,Z ==> Vertex 2 {m} + 1,1,0, !- X,Y,Z ==> Vertex 3 {m} + 0,1,0; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + space_wall2, !- Name + Wall, !- Surface Type + Interior Wall, !- Construction Name + ThermalZone, !- Zone Name + space, !- Space Name + Adiabatic, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 1,0,1, !- X,Y,Z ==> Vertex 1 {m} + 0,0,1, !- X,Y,Z ==> Vertex 2 {m} + 0,0,0, !- X,Y,Z ==> Vertex 3 {m} + 1,0,0; !- X,Y,Z ==> Vertex 4 {m} + + BuildingSurface:Detailed, + space_wall3, !- Name + Wall, !- Surface Type + Interior Wall, !- Construction Name + ThermalZone, !- Zone Name + space, !- Space Name + Adiabatic, !- Outside Boundary Condition + , !- Outside Boundary Condition Object + NoSun, !- Sun Exposure + NoWind, !- Wind Exposure + , !- View Factor to Ground + , !- Number of Vertices + 1,1,1, !- X,Y,Z ==> Vertex 1 {m} + 1,0,1, !- X,Y,Z ==> Vertex 2 {m} + 1,0,0, !- X,Y,Z ==> Vertex 3 {m} + 1,1,0; !- X,Y,Z ==> Vertex 4 {m} + +!- =========== ALL OBJECTS IN CLASS: ZONEHVAC:AIRDISTRIBUTIONUNIT =========== + + ZoneHVAC:AirDistributionUnit, + TunnelTerminal, !- Name + TunnelInlet, !- Air Distribution Unit Outlet Node Name + AirTerminal:SingleDuct:UserDefined, !- Air Terminal Object Type + AirMixer; !- Air Terminal Name + +!- =========== ALL OBJECTS IN CLASS: ZONEHVAC:EQUIPMENTLIST =========== + + ZoneHVAC:EquipmentList, + SolarHeatingSystem, !- Name + SequentialLoad, !- Load Distribution Scheme + ZoneHVAC:AirDistributionUnit, !- Zone Equipment 1 Object Type + TunnelTerminal, !- Zone Equipment 1 Name + 3, !- Zone Equipment 1 Cooling Sequence + 3, !- Zone Equipment 1 Heating or No-Load Sequence + , !- Zone Equipment 1 Sequential Cooling Fraction Schedule Name + , !- Zone Equipment 1 Sequential Heating Fraction Schedule Name + ZoneHVAC:ForcedAir:UserDefined, !- Zone Equipment 2 Object Type + IndirectSolarAirHeater, !- Zone Equipment 2 Name + 2, !- Zone Equipment 2 Cooling Sequence + 2, !- Zone Equipment 2 Heating or No-Load Sequence + , !- Zone Equipment 2 Sequential Cooling Fraction Schedule Name + , !- Zone Equipment 2 Sequential Heating Fraction Schedule Name + ZoneHVAC:ForcedAir:UserDefined, !- Zone Equipment 3 Object Type + DirectSolarAirHeater, !- Zone Equipment 3 Name + 1, !- Zone Equipment 3 Cooling Sequence + 1; !- Zone Equipment 3 Heating or No-Load Sequence + +!- =========== ALL OBJECTS IN CLASS: ZONEHVAC:EQUIPMENTCONNECTIONS =========== + + ZoneHVAC:EquipmentConnections, + ThermalZone, !- Zone Name + SolarHeatingSystem, !- Zone Conditioning Equipment List Name + TunnelInlet, !- Zone Air Inlet Node or NodeList Name + , !- Zone Air Exhaust Node or NodeList Name + TunnelZoneNode; !- Zone Air Node Name + +!- =========== ALL OBJECTS IN CLASS: OUTDOORAIR:NODE =========== + + OutdoorAir:Node, + OA_DSAH_Node; !- Name + + OutdoorAir:Node, + OA_ISAH_Node; !- Name + +!- =========== ALL OBJECTS IN CLASS: ZONEHVAC:FORCEDAIR:USERDEFINED =========== + + ZoneHVAC:ForcedAir:UserDefined, + DirectSolarAirHeater, !- Name + DirectSolarHeatingEMS, !- Overall Model Simulation Program Calling Manager Name + , !- Model Setup and Sizing Program Calling Manager Name + OA_DSAH_Node, !- Primary Air Inlet Node Name + DSAHOutletNode, !- Primary Air Outlet Node Name + , !- Secondary Air Inlet Node Name + , !- Secondary Air Outlet Node Name + 0; !- Number of Plant Loop Connections + + ZoneHVAC:ForcedAir:UserDefined, + IndirectSolarAirHeater, !- Name + IndirectSolarHeatingEMS, !- Overall Model Simulation Program Calling Manager Name + , !- Model Setup and Sizing Program Calling Manager Name + OA_ISAH_Node, !- Primary Air Inlet Node Name + ISAHOutletNode, !- Primary Air Outlet Node Name + , !- Secondary Air Inlet Node Name + , !- Secondary Air Outlet Node Name + 0; !- Number of Plant Loop Connections + +!- =========== ALL OBJECTS IN CLASS: AIRTERMINAL:SINGLEDUCT:USERDEFINED =========== + + AirTerminal:SingleDuct:UserDefined, + AirMixer, !- Name + AirMixingEMS, !- Overall Model Simulation Program Calling Manager Name + , !- Model Setup and Sizing Program Calling Manager Name + DSAHOutletNode, !- Primary Air Inlet Node Name + TunnelInlet, !- Primary Air Outlet Node Name + ISAHOutletNode, !- Secondary Air Inlet Node Name + , !- Secondary Air Outlet Node Name + 0, !- Number of Plant Loop Connections + AMXRPlantInletNode, !- Plant Connection 1 Inlet Node Name + AMXRPlantOutletNode; !- Plant Connection 1 Outlet Node Name + +!- =========== ALL OBJECTS IN CLASS: OUTPUT:VARIABLEDICTIONARY =========== + + Output:VariableDictionary,IDF,Unsorted; + +!- =========== ALL OBJECTS IN CLASS: OUTPUT:TABLE:SUMMARYREPORTS =========== + + Output:Table:SummaryReports, + AllSummary; !- Report 1 Name + +!- =========== ALL OBJECTS IN CLASS: OUTPUTCONTROL:TABLE:STYLE =========== + + OutputControl:Table:Style, + HTML; !- Column Separator + +!- =========== ALL OBJECTS IN CLASS: OUTPUT:VARIABLE =========== + + Output:Variable,*,Site Outdoor Air Drybulb Temperature,Timestep; + + Output:Variable,*,Site Outdoor Air Humidity Ratio,Timestep; + + Output:Variable,*,System Node Temperature,Timestep; + + Output:Variable,*,System Node Humidity Ratio,Timestep; + + Output:Variable,*,System Node Mass Flow Rate,Timestep; + +!- =========== ALL OBJECTS IN CLASS: OUTPUT:SQLITE =========== + + Output:SQLite, + SimpleAndTabular; !- Option Type + +!- =========== ALL OBJECTS IN CLASS: PYTHONPLUGIN:INSTANCE =========== + + PythonPlugin:Instance, + DirectSolarHeatingEMS, !- Name + No, !- Run During Warmup Days + PythonPluginSolarHeating,!- Python Module Name + DirectSolarHeating; !- Plugin Class Name + + PythonPlugin:Instance, + IndirectSolarHeatingEMS, !- Name + No, !- Run During Warmup Days + PythonPluginSolarHeating,!- Python Module Name + IndirectSolarHeating; !- Plugin Class Name + + PythonPlugin:Instance, + AirMixingEMS, !- Name + No, !- Run During Warmup Days + PythonPluginSolarHeating,!- Python Module Name + AirMixing; !- Plugin Class Name + diff --git a/testfiles/PythonPluginSolarHeating.py b/testfiles/PythonPluginSolarHeating.py new file mode 100644 index 00000000000..ab75a864e4c --- /dev/null +++ b/testfiles/PythonPluginSolarHeating.py @@ -0,0 +1,357 @@ +# EnergyPlus, Copyright (c) 1996-2026, The Board of Trustees of the University +# of Illinois, The Regents of the University of California, through Lawrence +# Berkeley National Laboratory (subject to receipt of any required approvals +# from the U.S. Dept. of Energy), Oak Ridge National Laboratory, managed by UT- +# Battelle, Alliance for Energy Innovation, LLC, and other contributors. All +# rights reserved. +# +# NOTICE: This Software was developed under funding from the U.S. Department of +# Energy and the U.S. Government consequently retains certain rights. As such, +# the U.S. Government has been granted for itself and others acting on its +# behalf a paid-up, nonexclusive, irrevocable, worldwide license in the +# Software to reproduce, distribute copies to the public, prepare derivative +# works, and perform publicly and display publicly, and to permit others to do +# so. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# (1) Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# (2) Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# (3) Neither the name of the University of California, Lawrence Berkeley +# National Laboratory, the University of Illinois, U.S. Dept. of Energy nor +# the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in +# stand-alone form without changes from the version obtained under this +# License, or (ii) Licensee makes a reference solely to the software +# portion of its product, Licensee must refer to the software as +# "EnergyPlus version X" software, where "X" is the version number Licensee +# obtained under this License and may not use a different name for the +# software. Except as specifically required in this Section (4), Licensee +# shall not use in a company name, a product name, in advertising, +# publicity, or other promotional activities any name, trade name, +# trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or +# confusingly similar designation, without the U.S. Department of Energy's +# prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import math +import sys + +from pyenergyplus.plugin import EnergyPlusPlugin + + +class DirectSolarHeating(EnergyPlusPlugin): + + def __init__(self): + # init parent class + super().__init__() + + # members + self.DSAH_MdotIn = 0.0 + self.DSAH_MdotOut = 0.0 + self.DSAH_Tinlet = 0.0 + self.DSAH_Winlet = 0.0 + self.DSAH_Toutlet = 0.0 + self.DSAH_Woutlet = 0.0 + + # handles + self.need_to_get_handles = True + self.handles = {} + + # psych api instance + self.psych = None + + def get_handles(self, state): + self.handles["DSAH_Tinlet"] = self.api.exchange.get_internal_variable_handle( + state, "Inlet Temperature for Primary Air Connection", "DirectSolarAirHeater" + ) + + self.handles["DSAH_Winlet"] = self.api.exchange.get_internal_variable_handle( + state, "Inlet Humidity Ratio for Primary Air Connection", "DirectSolarAirHeater" + ) + + self.handles["DSAH_MdotOut"] = self.api.exchange.get_actuator_handle( + state, "Primary Air Connection", "Outlet Mass Flow Rate", "DirectSolarAirHeater" + ) + self.handles["DSAH_MdotIn"] = self.api.exchange.get_actuator_handle( + state, "Primary Air Connection", "Inlet Mass Flow Rate", "DirectSolarAirHeater" + ) + self.handles["DSAH_Toutlet"] = self.api.exchange.get_actuator_handle( + state, "Primary Air Connection", "Outlet Temperature", "DirectSolarAirHeater" + ) + self.handles["DSAH_Woutlet"] = self.api.exchange.get_actuator_handle( + state, "Primary Air Connection", "Outlet Humidity Ratio", "DirectSolarAirHeater" + ) + self.need_to_get_handles = False + + def handles_gotten_properly(self, state): + handles_ok = True + + for k, v in self.handles.items(): + if v == -1: + handles_ok = False + self.api.runtime.issue_severe(state, f"Handle not found for '{k}'") + + return handles_ok + + def initialize(self, state): + self.DSAH_Tinlet = self.api.exchange.get_internal_variable_value(state, self.handles["DSAH_Tinlet"]) + self.DSAH_Winlet = self.api.exchange.get_internal_variable_value(state, self.handles["DSAH_Winlet"]) + + def simulate(self, state): + # First simulation, assumed a predefined Heating Capacity, the DSAH will heat the air + # according to it + HCapacity = 3000 # W + QDesign = 1.0 # m^3/s + Patm = 77700 # Pa + VspIn = self.psych.specific_volume(state, self.DSAH_Tinlet, self.DSAH_Winlet, Patm) + self.DSAH_MdotIn = QDesign / VspIn + self.DSAH_MdotOut = self.DSAH_MdotIn + hInlet = self.psych.enthalpy(state, self.DSAH_Tinlet, self.DSAH_Winlet) + hOutlet = hInlet + HCapacity / self.DSAH_MdotOut + self.DSAH_Toutlet = self.psych.dry_bulb(state, hOutlet, self.DSAH_Winlet) + self.DSAH_Woutlet = self.DSAH_Winlet + + def report(self, state): + self.api.exchange.set_actuator_value(state, self.handles["DSAH_MdotOut"], self.DSAH_MdotOut) + self.api.exchange.set_actuator_value(state, self.handles["DSAH_MdotIn"], self.DSAH_MdotIn) + self.api.exchange.set_actuator_value(state, self.handles["DSAH_Toutlet"], self.DSAH_Toutlet) + self.api.exchange.set_actuator_value(state, self.handles["DSAH_Woutlet"], self.DSAH_Woutlet) + + def on_user_defined_component_model(self, state) -> int: + if not self.psych: + self.psych = self.api.functional.psychrometrics(state) + if self.need_to_get_handles: + self.get_handles(state) + if not self.handles_gotten_properly(state): + return 1 + self.initialize(state) + self.simulate(state) + self.report(state) + return 0 + + +class IndirectSolarHeating(EnergyPlusPlugin): + + def __init__(self): + # init parent class + super().__init__() + + # members + self.ISAH_MdotIn = 0.0 + self.ISAH_MdotOut = 0.0 + self.ISAH_Tinlet = 0.0 + self.ISAH_Winlet = 0.0 + self.ISAH_Toutlet = 0.0 + self.ISAH_Woutlet = 0.0 + + # handles + self.need_to_get_handles = True + self.handles = {} + + # psych api instance + self.psych = None + + def get_handles(self, state): + self.handles["ISAH_Tinlet"] = self.api.exchange.get_internal_variable_handle( + state, "Inlet Temperature for Primary Air Connection", "IndirectSolarAirHeater" + ) + + self.handles["ISAH_Winlet"] = self.api.exchange.get_internal_variable_handle( + state, "Inlet Humidity Ratio for Primary Air Connection", "IndirectSolarAirHeater" + ) + + self.handles["ISAH_MdotOut"] = self.api.exchange.get_actuator_handle( + state, "Primary Air Connection", "Outlet Mass Flow Rate", "IndirectSolarAirHeater" + ) + self.handles["ISAH_MdotIn"] = self.api.exchange.get_actuator_handle( + state, "Primary Air Connection", "Inlet Mass Flow Rate", "IndirectSolarAirHeater" + ) + self.handles["ISAH_Toutlet"] = self.api.exchange.get_actuator_handle( + state, "Primary Air Connection", "Outlet Temperature", "IndirectSolarAirHeater" + ) + self.handles["ISAH_Woutlet"] = self.api.exchange.get_actuator_handle( + state, "Primary Air Connection", "Outlet Humidity Ratio", "IndirectSolarAirHeater" + ) + self.need_to_get_handles = False + + def handles_gotten_properly(self, state): + handles_ok = True + + for k, v in self.handles.items(): + if v == -1: + handles_ok = False + self.api.runtime.issue_severe(state, f"Handle not found for '{k}'") + + return handles_ok + + def initialize(self, state): + self.ISAH_Tinlet = self.api.exchange.get_internal_variable_value(state, self.handles["ISAH_Tinlet"]) + self.ISAH_Winlet = self.api.exchange.get_internal_variable_value(state, self.handles["ISAH_Winlet"]) + + def simulate(self, state): + # First simulation, assumed a predefined Evaporative Cooling Effectiveness of 0.6 + QDesign = 2.0 # m^3/s + Patm = 77700 # Pa + VspIn = self.psych.specific_volume(state, self.ISAH_Tinlet, self.ISAH_Winlet, Patm) + self.ISAH_MdotIn = QDesign / VspIn + self.ISAH_MdotOut = self.ISAH_MdotIn + Twb = self.psych.wet_bulb(state, self.ISAH_Tinlet, self.ISAH_Winlet, Patm) + self.ISAH_Toutlet = 0.6 * (self.ISAH_Tinlet - Twb) + self.ISAH_Woutlet = self.psych.humidity_ratio_d(state, self.ISAH_Tinlet, Twb, Patm) + + def report(self, state): + self.api.exchange.set_actuator_value(state, self.handles["ISAH_MdotOut"], self.ISAH_MdotOut) + self.api.exchange.set_actuator_value(state, self.handles["ISAH_MdotIn"], self.ISAH_MdotIn) + self.api.exchange.set_actuator_value(state, self.handles["ISAH_Toutlet"], self.ISAH_Toutlet) + self.api.exchange.set_actuator_value(state, self.handles["ISAH_Woutlet"], self.ISAH_Woutlet) + + def on_user_defined_component_model(self, state) -> int: + if not self.psych: + self.psych = self.api.functional.psychrometrics(state) + if self.need_to_get_handles: + self.get_handles(state) + if not self.handles_gotten_properly(state): + return 1 + self.initialize(state) + self.simulate(state) + self.report(state) + return 0 + + +class AirMixing(EnergyPlusPlugin): + + def __init__(self): + # init parent class + super().__init__() + + # members + self.AMXR_MdotIn1 = 0.0 + self.AMXR_MdotIn2 = 0.0 + self.AMXR_MdotOut = 0.0 + self.AMXR_Tinlet1 = 0.0 + self.AMXR_Winlet1 = 0.0 + self.AMXR_Tinlet2 = 0.0 + self.AMXR_Winlet2 = 0.0 + self.AMXR_Toutlet = 0.0 + self.AMXR_Woutlet = 0.0 + + # handles + self.need_to_get_handles = True + self.handles = {} + + # psych api instance + self.psych = None + + def get_handles(self, state): + self.handles["AMXR_MdotIn1"] = self.api.exchange.get_variable_handle( + state, "System Node Mass Flow Rate", "DSAHOutletNode" + ) + # + self.handles["AMXR_MdotIn2"] = self.api.exchange.get_variable_handle( + state, "System Node Mass Flow Rate", "ISAHOutletNode" + ) + + self.handles["AMXR_Tinlet1"] = self.api.exchange.get_internal_variable_handle( + state, "Inlet Temperature for Primary Air Connection", "AirMixer" + ) + + self.handles["AMXR_Winlet1"] = self.api.exchange.get_internal_variable_handle( + state, "Inlet Humidity Ratio for Primary Air Connection", "AirMixer" + ) + + self.handles["AMXR_Tinlet2"] = self.api.exchange.get_internal_variable_handle( + state, "Inlet Temperature for Secondary Air Connection", "AirMixer" + ) + + self.handles["AMXR_Winlet2"] = self.api.exchange.get_internal_variable_handle( + state, "Inlet Humidity Ratio for Secondary Air Connection", "AirMixer" + ) + + self.handles["AMXR_MdotOut"] = self.api.exchange.get_actuator_handle( + state, "Primary Air Connection", "Outlet Mass Flow Rate", "AirMixer" + ) + + self.handles["AMXR_Toutlet"] = self.api.exchange.get_actuator_handle( + state, "Primary Air Connection", "Outlet Temperature", "AirMixer" + ) + + self.handles["AMXR_Woutlet"] = self.api.exchange.get_actuator_handle( + state, "Primary Air Connection", "Outlet Humidity Ratio", "AirMixer" + ) + + self.need_to_get_handles = False + + def handles_gotten_properly(self, state): + handles_ok = True + + for k, v in self.handles.items(): + if v == -1: + handles_ok = False + self.api.runtime.issue_severe(state, f"Handle not found for '{k}'") + + return handles_ok + + def initialize(self, state): + self.AMXR_Tinlet1 = self.api.exchange.get_internal_variable_value(state, self.handles["AMXR_Tinlet1"]) + self.AMXR_Winlet1 = self.api.exchange.get_internal_variable_value(state, self.handles["AMXR_Winlet1"]) + self.AMXR_MdotIn1 = self.api.exchange.get_variable_value(state, self.handles["AMXR_MdotIn1"]) + self.AMXR_Tinlet2 = self.api.exchange.get_internal_variable_value(state, self.handles["AMXR_Tinlet2"]) + self.AMXR_Winlet2 = self.api.exchange.get_internal_variable_value(state, self.handles["AMXR_Winlet1"]) + self.AMXR_MdotIn2 = self.api.exchange.get_variable_value(state, self.handles["AMXR_MdotIn2"]) + + def simulate(self, state): + # Second simulation, the function will find output conditions of a mixed air + self.AMXR_MdotOut = self.AMXR_MdotIn1 + self.AMXR_MdotIn2 + try: + rmix = self.AMXR_MdotIn2 / self.AMXR_MdotIn1 + h1 = self.psych.enthalpy(self.AMXR_Tinlet1, self.AMXR_Winlet1) + h2 = self.psych.enthalpy(self.AMXR_Tinlet2, self.AMXR_Winlet2) + h3 = h1 + rmix * (h2 - h1) + self.AMXR_Woutlet = self.AMXR_Winlet1 + rmix * (self.AMXR_Winlet2 - self.AMXR_Winlet1) + self.AMXR_Toutlet = self.psych.dry_bulb(h3, self.AMXR_Woutlet) + except ZeroDivisionError as e: + if self.AMXR_MdotIn1 == 0.0 and self.AMXR_MdotIn2 == 0.0: + self.AMXR_MdotOut = 0.0 + self.AMXR_Woutlet = (self.AMXR_Winlet1 + self.AMXR_Winlet2) / 2.0 + self.AMXR_Toutlet = (self.AMXR_Tinlet1 + self.AMXR_Tinlet2) / 2.0 + else: + self.AMXR_MdotOut = self.AMXR_MdotIn2 + self.AMXR_Woutlet = self.AMXR_Winlet2 + self.AMXR_Toutlet = self.AMXR_Tinlet2 + + def report(self, state): + self.api.exchange.set_actuator_value(state, self.handles["AMXR_MdotOut"], self.AMXR_MdotOut) + self.api.exchange.set_actuator_value(state, self.handles["AMXR_Toutlet"], self.AMXR_Toutlet) + self.api.exchange.set_actuator_value(state, self.handles["AMXR_Woutlet"], self.AMXR_Woutlet) + + def on_begin_zone_timestep_before_init_heat_balance(self, state) -> int: + if not self.psych: + self.psych = self.api.functional.psychrometrics(state) + if self.need_to_get_handles: + self.get_handles(state) + if not self.handles_gotten_properly(state): + return 1 + self.initialize(state) + self.simulate(state) + self.report(state) + return 0