LCOV - code coverage report
Current view: top level - EnergyPlus - PluginManager.cc (source / functions) Hit Total Coverage
Test: lcov.output.filtered Lines: 403 777 51.9 %
Date: 2023-01-17 19:17:23 Functions: 26 33 78.8 %

          Line data    Source code
       1             : // EnergyPlus, Copyright (c) 1996-2023, The Board of Trustees of the University of Illinois,
       2             : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
       3             : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
       4             : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
       5             : // contributors. All rights reserved.
       6             : //
       7             : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
       8             : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
       9             : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
      10             : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
      11             : // derivative works, and perform publicly and display publicly, and to permit others to do so.
      12             : //
      13             : // Redistribution and use in source and binary forms, with or without modification, are permitted
      14             : // provided that the following conditions are met:
      15             : //
      16             : // (1) Redistributions of source code must retain the above copyright notice, this list of
      17             : //     conditions and the following disclaimer.
      18             : //
      19             : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
      20             : //     conditions and the following disclaimer in the documentation and/or other materials
      21             : //     provided with the distribution.
      22             : //
      23             : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
      24             : //     the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
      25             : //     used to endorse or promote products derived from this software without specific prior
      26             : //     written permission.
      27             : //
      28             : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
      29             : //     without changes from the version obtained under this License, or (ii) Licensee makes a
      30             : //     reference solely to the software portion of its product, Licensee must refer to the
      31             : //     software as "EnergyPlus version X" software, where "X" is the version number Licensee
      32             : //     obtained under this License and may not use a different name for the software. Except as
      33             : //     specifically required in this Section (4), Licensee shall not use in a company name, a
      34             : //     product name, in advertising, publicity, or other promotional activities any name, trade
      35             : //     name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
      36             : //     similar designation, without the U.S. Department of Energy's prior written consent.
      37             : //
      38             : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
      39             : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
      40             : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
      41             : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
      42             : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
      43             : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      44             : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
      45             : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      46             : // POSSIBILITY OF SUCH DAMAGE.
      47             : 
      48             : #include <EnergyPlus/Data/EnergyPlusData.hh>
      49             : #include <EnergyPlus/DataStringGlobals.hh>
      50             : #include <EnergyPlus/FileSystem.hh>
      51             : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
      52             : #include <EnergyPlus/OutputProcessor.hh>
      53             : #include <EnergyPlus/PluginManager.hh>
      54             : #include <EnergyPlus/UtilityRoutines.hh>
      55             : 
      56             : #include <algorithm>
      57             : #include <nlohmann/json.hpp>
      58             : 
      59             : namespace EnergyPlus::PluginManagement {
      60             : 
      61           4 : PluginTrendVariable::PluginTrendVariable(EnergyPlusData &state, std::string _name, int _numValues, int _indexOfPluginVariable)
      62           4 :     : name(std::move(_name)), numValues(_numValues), indexOfPluginVariable(_indexOfPluginVariable)
      63             : {
      64             :     // initialize the deque so it can be queried immediately, even with just zeroes
      65        1214 :     for (int i = 1; i <= this->numValues; i++) {
      66        1210 :         this->values.push_back(0);
      67             :     }
      68        1214 :     for (int loop = 1; loop <= _numValues; ++loop) {
      69        1210 :         this->times.push_back(-loop * state.dataGlobal->TimeStepZone);
      70             :     }
      71           4 : }
      72             : 
      73           0 : void registerNewCallback(EnergyPlusData &state, EMSManager::EMSCallFrom iCalledFrom, const std::function<void(void *)> &f)
      74             : {
      75           0 :     state.dataPluginManager->callbacks[iCalledFrom].push_back(f);
      76           0 : }
      77             : 
      78         139 : void onBeginEnvironment(EnergyPlusData &state)
      79             : {
      80             :     // reset vars and trends -- sensors and actuators are reset by EMS
      81         197 :     for (auto &v : state.dataPluginManager->globalVariableValues) {
      82          58 :         v = 0;
      83             :     }
      84             :     // reinitialize trend variables so old data are purged
      85         145 :     for (auto &tr : state.dataPluginManager->trends) {
      86           6 :         tr.reset();
      87             :     }
      88         139 : }
      89             : 
      90        2483 : int PluginManager::numActiveCallbacks(EnergyPlusData &state)
      91             : {
      92        2483 :     return (int)state.dataPluginManager->callbacks.size();
      93             : }
      94             : 
      95     3685788 : void runAnyRegisteredCallbacks(EnergyPlusData &state, EMSManager::EMSCallFrom iCalledFrom, bool &anyRan)
      96             : {
      97     3685788 :     if (state.dataGlobal->KickOffSimulation) return;
      98     3676043 :     for (auto const &cb : state.dataPluginManager->callbacks[iCalledFrom]) {
      99           0 :         cb((void *)&state);
     100           0 :         anyRan = true;
     101             :     }
     102             : #if LINK_WITH_PYTHON
     103     6523746 :     for (auto &plugin : state.dataPluginManager->plugins) {
     104     2847703 :         if (plugin.runDuringWarmup || !state.dataGlobal->WarmupFlag) {
     105     2835285 :             bool const didOneRun = plugin.run(state, iCalledFrom);
     106     2835285 :             if (didOneRun) anyRan = true;
     107             :         }
     108             :     }
     109             : #endif
     110             : }
     111             : 
     112             : #if LINK_WITH_PYTHON
     113         771 : std::string pythonStringForUsage(EnergyPlusData &state)
     114             : {
     115         771 :     if (state.dataGlobal->errorCallback) {
     116           0 :         return "Python Version not accessible during API calls";
     117             :     }
     118        1542 :     std::string sVersion = Py_GetVersion();
     119             :     // 3.8.3 (default, Jun  2 2020, 15:25:16) \n[GCC 7.5.0]
     120             :     // Remove the '\n'
     121         771 :     sVersion.erase(std::remove(sVersion.begin(), sVersion.end(), '\n'), sVersion.end());
     122         771 :     return "Linked to Python Version: \"" + sVersion + "\"";
     123             : }
     124             : #else
     125             : std::string pythonStringForUsage([[maybe_unused]] EnergyPlusData &state)
     126             : {
     127             :     return "This version of EnergyPlus not linked to Python library.";
     128             : }
     129             : #endif
     130             : 
     131         769 : void PluginManager::setupOutputVariables([[maybe_unused]] EnergyPlusData &state)
     132             : {
     133             : #if LINK_WITH_PYTHON
     134             :     // with the PythonPlugin:Variables all set in memory, we can now set them up as outputs as needed
     135        1538 :     std::string const sOutputVariable = "PythonPlugin:OutputVariable";
     136         769 :     int outputVarInstances = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, sOutputVariable);
     137         769 :     if (outputVarInstances > 0) {
     138          22 :         auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(sOutputVariable);
     139          11 :         if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) {
     140             :             ShowSevereError(state, sOutputVariable + ": Somehow getNumObjectsFound was > 0 but epJSON.find found 0"); // LCOV_EXCL_LINE
     141             :         }
     142          11 :         auto &instancesValue = instances.value();
     143          28 :         for (auto instance = instancesValue.begin(); instance != instancesValue.end(); ++instance) {
     144          17 :             auto const &fields = instance.value();
     145          17 :             auto const &thisObjectName = instance.key();
     146          34 :             auto const objNameUC = EnergyPlus::UtilityRoutines::MakeUPPERCase(thisObjectName);
     147             :             // no need to validate name, the JSON will validate that.
     148          17 :             state.dataInputProcessing->inputProcessor->markObjectAsUsed(sOutputVariable, thisObjectName);
     149          34 :             std::string varName = fields.at("python_plugin_variable_name").get<std::string>();
     150          34 :             std::string avgOrSum = EnergyPlus::UtilityRoutines::MakeUPPERCase(fields.at("type_of_data_in_variable").get<std::string>());
     151          34 :             std::string updateFreq = EnergyPlus::UtilityRoutines::MakeUPPERCase(fields.at("update_frequency").get<std::string>());
     152          34 :             std::string units;
     153          17 :             if (fields.find("units") != fields.end()) {
     154          13 :                 units = fields.at("units").get<std::string>();
     155             :             }
     156             :             // get the index of the global variable, fatal if it doesn't mach one
     157             :             // validate type of data, update frequency, and look up units enum value
     158             :             // call setup output variable - variable TYPE is "PythonPlugin:OutputVariable"
     159          17 :             int variableHandle = EnergyPlus::PluginManagement::PluginManager::getGlobalVariableHandle(state, varName);
     160          17 :             if (variableHandle == -1) {
     161           0 :                 EnergyPlus::ShowSevereError(state, "Failed to match Python Plugin Output Variable");
     162           0 :                 EnergyPlus::ShowContinueError(state, "Trying to create output instance for variable name \"" + varName + "\"");
     163           0 :                 EnergyPlus::ShowContinueError(state, "No match found, make sure variable is listed in PythonPlugin:Variables object");
     164           0 :                 EnergyPlus::ShowFatalError(state, "Python Plugin Output Variable problem causes program termination");
     165             :             }
     166          17 :             bool isMetered = false;
     167          17 :             OutputProcessor::SOVStoreType sAvgOrSum = OutputProcessor::SOVStoreType::Average;
     168          17 :             if (avgOrSum == "SUMMED") {
     169           0 :                 sAvgOrSum = OutputProcessor::SOVStoreType::Summed;
     170          17 :             } else if (avgOrSum == "METERED") {
     171           2 :                 sAvgOrSum = OutputProcessor::SOVStoreType::Summed;
     172           2 :                 isMetered = true;
     173             :             }
     174          17 :             OutputProcessor::SOVTimeStepType sUpdateFreq = OutputProcessor::SOVTimeStepType::Zone;
     175          17 :             if (updateFreq == "SYSTEMTIMESTEP") {
     176           9 :                 sUpdateFreq = OutputProcessor::SOVTimeStepType::System;
     177             :             }
     178          17 :             OutputProcessor::Unit thisUnit = OutputProcessor::Unit::None;
     179          17 :             if (!units.empty()) {
     180          13 :                 thisUnit = OutputProcessor::unitStringToEnum(units);
     181          13 :                 if (thisUnit == OutputProcessor::Unit::unknown) {
     182           4 :                     thisUnit = OutputProcessor::Unit::customEMS;
     183             :                 }
     184             :             }
     185          17 :             if (!isMetered) {
     186             :                 // regular output variable, ignore the meter/resource stuff and register the variable
     187          15 :                 if (thisUnit != OutputProcessor::Unit::customEMS) {
     188          22 :                     SetupOutputVariable(state,
     189             :                                         sOutputVariable,
     190             :                                         thisUnit,
     191          11 :                                         state.dataPluginManager->globalVariableValues[variableHandle],
     192             :                                         sUpdateFreq,
     193             :                                         sAvgOrSum,
     194             :                                         thisObjectName);
     195             :                 } else {
     196           8 :                     SetupOutputVariable(state,
     197             :                                         sOutputVariable,
     198             :                                         thisUnit,
     199           4 :                                         state.dataPluginManager->globalVariableValues[variableHandle],
     200             :                                         sUpdateFreq,
     201             :                                         sAvgOrSum,
     202             :                                         thisObjectName,
     203             :                                         _,
     204             :                                         _,
     205             :                                         _,
     206             :                                         _,
     207             :                                         _,
     208             :                                         _,
     209             :                                         _,
     210             :                                         _,
     211             :                                         _,
     212             :                                         units);
     213             :                 }
     214             :             } else {
     215             :                 // We are doing a metered type, we need to get the extra stuff
     216             :                 // Resource Type
     217           2 :                 if (fields.find("resource_type") == fields.end()) {
     218           0 :                     EnergyPlus::ShowSevereError(state, "Input error on PythonPlugin:OutputVariable = " + thisObjectName);
     219           0 :                     EnergyPlus::ShowContinueError(state, "The variable was marked as metered, but did not define a resource type");
     220           0 :                     EnergyPlus::ShowContinueError(state,
     221             :                                                   "For metered variables, the resource type, group type, and end use category must be defined");
     222           0 :                     EnergyPlus::ShowFatalError(state, "Input error on PythonPlugin:OutputVariable causes program termination");
     223             :                 }
     224           4 :                 std::string const resourceType = EnergyPlus::UtilityRoutines::MakeUPPERCase(fields.at("resource_type").get<std::string>());
     225           4 :                 std::string sResourceType;
     226           2 :                 if (resourceType == "ELECTRICITY") {
     227           2 :                     sResourceType = "Electricity";
     228           0 :                 } else if (resourceType == "NATURALGAS") {
     229           0 :                     sResourceType = "NaturalGas";
     230           0 :                 } else if (resourceType == "GASOLINE") {
     231           0 :                     sResourceType = "Gasoline";
     232           0 :                 } else if (resourceType == "DIESEL") {
     233           0 :                     sResourceType = "Diesel";
     234           0 :                 } else if (resourceType == "COAL") {
     235           0 :                     sResourceType = "Coal";
     236           0 :                 } else if (resourceType == "FUELOILNO1") {
     237           0 :                     sResourceType = "FuelOilNo1";
     238           0 :                 } else if (resourceType == "FUELOILNO2") {
     239           0 :                     sResourceType = "FuelOilNo2";
     240           0 :                 } else if (resourceType == "OTHERFUEL1") {
     241           0 :                     sResourceType = "OtherFuel1";
     242           0 :                 } else if (resourceType == "OTHERFUEL2") {
     243           0 :                     sResourceType = "OtherFuel2";
     244           0 :                 } else if (resourceType == "PROPANE") {
     245           0 :                     sResourceType = "Propane";
     246           0 :                 } else if (resourceType == "WATERUSE") {
     247           0 :                     sResourceType = "Water";
     248           0 :                 } else if (resourceType == "ONSITEWATERPRODUCED") {
     249           0 :                     sResourceType = "OnSiteWater";
     250           0 :                 } else if (resourceType == "MAINSWATERSUPPLY") {
     251           0 :                     sResourceType = "MainsWater";
     252           0 :                 } else if (resourceType == "RAINWATERCOLLECTED") {
     253           0 :                     sResourceType = "RainWater";
     254           0 :                 } else if (resourceType == "WELLWATERDRAWN") {
     255           0 :                     sResourceType = "WellWater";
     256           0 :                 } else if (resourceType == "CONDENSATEWATERCOLLECTED") {
     257           0 :                     sResourceType = "Condensate";
     258           0 :                 } else if (resourceType == "ENERGYTRANSFER") {
     259           0 :                     sResourceType = "EnergyTransfer";
     260           0 :                 } else if (resourceType == "STEAM") {
     261           0 :                     sResourceType = "Steam";
     262           0 :                 } else if (resourceType == "DISTRICTCOOLING") {
     263           0 :                     sResourceType = "DistrictCooling";
     264           0 :                 } else if (resourceType == "DISTRICTHEATING") {
     265           0 :                     sResourceType = "DistrictHeating";
     266           0 :                 } else if (resourceType == "ELECTRICITYPRODUCEDONSITE") {
     267           0 :                     sResourceType = "ElectricityProduced";
     268           0 :                 } else if (resourceType == "SOLARWATERHEATING") {
     269           0 :                     sResourceType = "SolarWater";
     270           0 :                 } else if (resourceType == "SOLARAIRHEATING") {
     271           0 :                     sResourceType = "SolarAir";
     272             :                 } else {
     273           0 :                     ShowSevereError(state, "Invalid input for PythonPlugin:OutputVariable, unexpected Resource Type = " + resourceType);
     274           0 :                     ShowFatalError(state, "Python plugin output variable input problem causes program termination");
     275             :                 }
     276             : 
     277             :                 // Group Type
     278           2 :                 if (fields.find("group_type") == fields.end()) {
     279           0 :                     EnergyPlus::ShowSevereError(state, "Input error on PythonPlugin:OutputVariable = " + thisObjectName);
     280           0 :                     EnergyPlus::ShowContinueError(state, "The variable was marked as metered, but did not define a group type");
     281           0 :                     EnergyPlus::ShowContinueError(state,
     282             :                                                   "For metered variables, the resource type, group type, and end use category must be defined");
     283           0 :                     EnergyPlus::ShowFatalError(state, "Input error on PythonPlugin:OutputVariable causes program termination");
     284             :                 }
     285           4 :                 std::string const groupType = EnergyPlus::UtilityRoutines::MakeUPPERCase(fields.at("group_type").get<std::string>());
     286           4 :                 std::string sGroupType;
     287           2 :                 if (groupType == "BUILDING") {
     288           0 :                     sGroupType = "Building";
     289           2 :                 } else if (groupType == "HVAC") {
     290           2 :                     sGroupType = "HVAC";
     291           0 :                 } else if (groupType == "PLANT") {
     292           0 :                     sGroupType = "Plant";
     293           0 :                 } else if (groupType == "SYSTEM") {
     294           0 :                     sGroupType = "System";
     295             :                 } else {
     296           0 :                     ShowSevereError(state, "Invalid input for PythonPlugin:OutputVariable, unexpected Group Type = " + groupType);
     297           0 :                     ShowFatalError(state, "Python plugin output variable input problem causes program termination");
     298             :                 }
     299             : 
     300             :                 // End Use Type
     301           2 :                 if (fields.find("end_use_category") == fields.end()) {
     302           0 :                     EnergyPlus::ShowSevereError(state, "Input error on PythonPlugin:OutputVariable = " + thisObjectName);
     303           0 :                     EnergyPlus::ShowContinueError(state, "The variable was marked as metered, but did not define an end-use category");
     304           0 :                     EnergyPlus::ShowContinueError(state,
     305             :                                                   "For metered variables, the resource type, group type, and end use category must be defined");
     306           0 :                     EnergyPlus::ShowFatalError(state, "Input error on PythonPlugin:OutputVariable causes program termination");
     307             :                 }
     308           4 :                 std::string const endUse = EnergyPlus::UtilityRoutines::MakeUPPERCase(fields.at("end_use_category").get<std::string>());
     309           4 :                 std::string sEndUse;
     310           2 :                 if (endUse == "HEATING") {
     311           0 :                     sEndUse = "Heating";
     312           2 :                 } else if (endUse == "COOLING") {
     313           2 :                     sEndUse = "Cooling";
     314           0 :                 } else if (endUse == "INTERIORLIGHTS") {
     315           0 :                     sEndUse = "InteriorLights";
     316           0 :                 } else if (endUse == "EXTERIORLIGHTS") {
     317           0 :                     sEndUse = "ExteriorLights";
     318           0 :                 } else if (endUse == "INTERIOREQUIPMENT") {
     319           0 :                     sEndUse = "InteriorEquipment";
     320           0 :                 } else if (endUse == "EXTERIOREQUIPMENT") {
     321           0 :                     sEndUse = "ExteriorEquipment";
     322           0 :                 } else if (endUse == "FANS") {
     323           0 :                     sEndUse = "Fans";
     324           0 :                 } else if (endUse == "PUMPS") {
     325           0 :                     sEndUse = "Pumps";
     326           0 :                 } else if (endUse == "HEATREJECTION") {
     327           0 :                     sEndUse = "HeatRejection";
     328           0 :                 } else if (endUse == "HUMIDIFIER") {
     329           0 :                     sEndUse = "Humidifier";
     330           0 :                 } else if (endUse == "HEATRECOVERY") {
     331           0 :                     sEndUse = "HeatRecovery";
     332           0 :                 } else if (endUse == "WATERSYSTEMS") {
     333           0 :                     sEndUse = "WaterSystems";
     334           0 :                 } else if (endUse == "REFRIGERATION") {
     335           0 :                     sEndUse = "Refrigeration";
     336           0 :                 } else if (endUse == "ONSITEGENERATION") {
     337           0 :                     sEndUse = "Cogeneration";
     338           0 :                 } else if (endUse == "HEATINGCOILS") {
     339           0 :                     sEndUse = "HeatingCoils";
     340           0 :                 } else if (endUse == "COOLINGCOILS") {
     341           0 :                     sEndUse = "CoolingCoils";
     342           0 :                 } else if (endUse == "CHILLERS") {
     343           0 :                     sEndUse = "Chillers";
     344           0 :                 } else if (endUse == "BOILERS") {
     345           0 :                     sEndUse = "Boilers";
     346           0 :                 } else if (endUse == "BASEBOARD") {
     347           0 :                     sEndUse = "Baseboard";
     348           0 :                 } else if (endUse == "HEATRECOVERYFORCOOLING") {
     349           0 :                     sEndUse = "HeatRecoveryForCooling";
     350           0 :                 } else if (endUse == "HEATRECOVERYFORHEATING") {
     351           0 :                     sEndUse = "HeatRecoveryForHeating";
     352             :                 } else {
     353           0 :                     ShowSevereError(state, "Invalid input for PythonPlugin:OutputVariable, unexpected End-use Subcategory = " + groupType);
     354           0 :                     ShowFatalError(state, "Python plugin output variable input problem causes program termination");
     355             :                 }
     356             : 
     357             :                 // Additional End Use Types Only Used for EnergyTransfer
     358           4 :                 if ((sResourceType != "EnergyTransfer") &&
     359           6 :                     (sEndUse == "HeatingCoils" || sEndUse == "CoolingCoils" || sEndUse == "Chillers" || sEndUse == "Boilers" ||
     360           4 :                      sEndUse == "Baseboard" || sEndUse == "HeatRecoveryForCooling" || sEndUse == "HeatRecoveryForHeating")) {
     361           0 :                     ShowWarningError(state, "Inconsistent resource type input for PythonPlugin:OutputVariable = " + thisObjectName);
     362           0 :                     ShowContinueError(state, "For end use subcategory = " + sEndUse + ", resource type must be EnergyTransfer");
     363           0 :                     ShowContinueError(state, "Resource type is being reset to EnergyTransfer and the simulation continues...");
     364           0 :                     sResourceType = "EnergyTransfer";
     365             :                 }
     366             : 
     367           4 :                 std::string sEndUseSubcategory;
     368           2 :                 if (fields.find("end_use_subcategory") != fields.end()) {
     369           2 :                     sEndUseSubcategory = fields.at("end_use_subcategory").get<std::string>();
     370             :                 }
     371             : 
     372           2 :                 if (sEndUseSubcategory.empty()) { // no subcategory
     373           0 :                     SetupOutputVariable(state,
     374             :                                         sOutputVariable,
     375             :                                         thisUnit,
     376           0 :                                         state.dataPluginManager->globalVariableValues[variableHandle],
     377             :                                         sUpdateFreq,
     378             :                                         sAvgOrSum,
     379             :                                         thisObjectName,
     380             :                                         _,
     381             :                                         sResourceType,
     382             :                                         sEndUse,
     383             :                                         _,
     384             :                                         sGroupType);
     385             :                 } else { // has subcategory
     386           4 :                     SetupOutputVariable(state,
     387             :                                         sOutputVariable,
     388             :                                         thisUnit,
     389           2 :                                         state.dataPluginManager->globalVariableValues[variableHandle],
     390             :                                         sUpdateFreq,
     391             :                                         sAvgOrSum,
     392             :                                         thisObjectName,
     393             :                                         _,
     394             :                                         sResourceType,
     395             :                                         sEndUse,
     396             :                                         sEndUseSubcategory,
     397             :                                         sGroupType);
     398             :                 }
     399             :             }
     400             :         }
     401             :     }
     402             : #endif
     403         769 : }
     404             : 
     405         771 : PluginManager::PluginManager(EnergyPlusData &state) : eplusRunningViaPythonAPI(state.dataPluginManager->eplusRunningViaPythonAPI)
     406             : {
     407             : #if LINK_WITH_PYTHON
     408             :     // this frozen flag tells Python that the package and library have been frozen for embedding, so it shouldn't warn about missing prefixes
     409         771 :     Py_FrozenFlag = 1;
     410             : 
     411             :     // we'll need the program directory for a few things so get it once here at the top and sanitize it
     412        1542 :     fs::path programDir;
     413         771 :     if (state.dataGlobal->installRootOverride) {
     414           0 :         programDir = state.dataStrGlobals->exeDirectoryPath;
     415             :     } else {
     416         771 :         programDir = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath()));
     417             :     }
     418        1542 :     fs::path sanitizedProgramDir = PluginManager::sanitizedPath(programDir);
     419             : 
     420             :     // I think we need to set the python path before initializing the library
     421             :     // make this relative to the binary
     422        1542 :     fs::path pathToPythonPackages = FileSystem::makeNativePath(sanitizedProgramDir / "python_standard_lib");
     423         771 :     wchar_t *a = Py_DecodeLocale(pathToPythonPackages.string().c_str(), nullptr);
     424         771 :     Py_SetPath(a);
     425         771 :     Py_SetPythonHome(a);
     426             : 
     427             :     // must be called before Py_Initialize
     428             :     // tells the interpreter the value of argv[0] to the main() function
     429             :     // used by some functions to find run-time libraries relative to the interpreter executable
     430         771 :     Py_SetProgramName((wchar_t *)programName);
     431             : 
     432             :     // now that we have set the path, we can initialize python
     433             :     // from https://docs.python.org/3/c-api/init.html
     434             :     // If arg 0, it skips init registration of signal handlers, which might be useful when Python is embedded.
     435         771 :     bool alreadyInitialized = (Py_IsInitialized() != 0);
     436         771 :     if (!alreadyInitialized) {
     437         771 :         Py_InitializeEx(0);
     438             :     }
     439             : 
     440             :     // Take control of the global interpreter lock while we are here, make sure to release it...
     441         771 :     PyGILState_STATE gil = PyGILState_Ensure();
     442             : 
     443             :     // call this once to allow us to add to, and report, sys.path later as needed
     444         771 :     PyRun_SimpleString("import sys"); // allows us to report sys.path later
     445             : 
     446             :     // we also need to set an extra import path to find some dynamic library loading stuff, again make it relative to the binary
     447        1542 :     fs::path pathToDynLoad = FileSystem::makeNativePath(sanitizedProgramDir / "python_standard_lib/lib-dynload");
     448        1542 :     fs::path libDirDynLoad = PluginManager::sanitizedPath(pathToDynLoad);
     449         771 :     PluginManager::addToPythonPath(state, libDirDynLoad, false);
     450             : 
     451             :     // now for additional paths:
     452             :     // we'll always want to add the program executable directory to PATH so that Python can find the installed pyenergyplus package
     453             :     // we will then optionally add the current working directory to allow Python to find scripts in the current directory
     454             :     // we will then optionally add the directory of the running IDF to allow Python to find scripts kept next to the IDF
     455             :     // we will then optionally add any additional paths the user specifies on the search paths object
     456             : 
     457             :     // so add the executable directory here
     458         771 :     PluginManager::addToPythonPath(state, sanitizedProgramDir, false);
     459             : 
     460             :     // Read all the additional search paths next
     461        1542 :     std::string const sPaths = "PythonPlugin:SearchPaths";
     462         771 :     int searchPaths = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, sPaths);
     463         771 :     if (searchPaths == 0) {
     464             :         // no search path objects in the IDF, just do the default behavior: add the current working dir and the input file dir
     465         771 :         PluginManager::addToPythonPath(state, ".", false);
     466        1542 :         fs::path sanitizedInputFileDir = PluginManager::sanitizedPath(state.dataStrGlobals->inputDirPath);
     467         771 :         PluginManager::addToPythonPath(state, sanitizedInputFileDir, false);
     468             :     }
     469         771 :     if (searchPaths > 0) {
     470           0 :         auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(sPaths);
     471           0 :         if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) {
     472             :             ShowSevereError(state,                                                                                   // LCOV_EXCL_LINE
     473             :                             "PythonPlugin:SearchPaths: Somehow getNumObjectsFound was > 0 but epJSON.find found 0"); // LCOV_EXCL_LINE
     474             :         }
     475           0 :         auto &instancesValue = instances.value();
     476           0 :         for (auto instance = instancesValue.begin(); instance != instancesValue.end(); ++instance) {
     477             :             // This is a unique object, so we should have one, but this is fine
     478           0 :             auto const &fields = instance.value();
     479           0 :             auto const &thisObjectName = instance.key();
     480           0 :             state.dataInputProcessing->inputProcessor->markObjectAsUsed(sPaths, thisObjectName);
     481           0 :             std::string workingDirFlagUC = "YES";
     482             :             try {
     483           0 :                 workingDirFlagUC =
     484           0 :                     EnergyPlus::UtilityRoutines::MakeUPPERCase(fields.at("add_current_working_directory_to_search_path").get<std::string>());
     485           0 :             } catch (nlohmann::json::out_of_range &e) {
     486             :                 // defaulted to YES
     487             :             }
     488           0 :             if (workingDirFlagUC == "YES") {
     489           0 :                 PluginManager::addToPythonPath(state, ".", false);
     490             :             }
     491           0 :             std::string inputFileDirFlagUC = "YES";
     492             :             try {
     493           0 :                 inputFileDirFlagUC =
     494           0 :                     EnergyPlus::UtilityRoutines::MakeUPPERCase(fields.at("add_input_file_directory_to_search_path").get<std::string>());
     495           0 :             } catch (nlohmann::json::out_of_range &e) {
     496             :                 // defaulted to YES
     497             :             }
     498           0 :             if (inputFileDirFlagUC == "YES") {
     499           0 :                 fs::path sanitizedInputFileDir = PluginManager::sanitizedPath(state.dataStrGlobals->inputDirPath);
     500           0 :                 PluginManager::addToPythonPath(state, sanitizedInputFileDir, false);
     501             :             }
     502             : 
     503           0 :             std::string epInDirFlagUC = "YES";
     504             :             try {
     505           0 :                 epInDirFlagUC =
     506           0 :                     EnergyPlus::UtilityRoutines::MakeUPPERCase(fields.at("add_epin_environment_variable_to_search_path").get<std::string>());
     507           0 :             } catch (nlohmann::json::out_of_range &e) {
     508             :                 // defaulted to YES
     509             :             }
     510           0 :             if (epInDirFlagUC == "YES") {
     511           0 :                 std::string epin_path;
     512           0 :                 get_environment_variable("epin", epin_path);
     513           0 :                 fs::path epinPathObject = fs::path(epin_path);
     514           0 :                 if (epinPathObject.empty()) {
     515           0 :                     EnergyPlus::ShowWarningMessage(
     516             :                         state,
     517             :                         "PluginManager: Search path inputs requested adding epin variable to Python path, but epin variable was empty, skipping.");
     518             :                 } else {
     519           0 :                     auto epinRootDir = FileSystem::getParentDirectoryPath(fs::path(epinPathObject));
     520           0 :                     if (FileSystem::pathExists(epinRootDir)) {
     521           0 :                         fs::path sanitizedEnvInputDir = PluginManager::sanitizedPath(epinRootDir);
     522           0 :                         PluginManager::addToPythonPath(state, sanitizedEnvInputDir, true);
     523             :                     } else {
     524           0 :                         EnergyPlus::ShowWarningMessage(state,
     525             :                                                        "PluginManager: Search path inputs requested adding epin variable to Python path, but epin "
     526             :                                                        "variable value is not a valid existent path, skipping.");
     527             :                     }
     528             :                 }
     529             :             }
     530             : 
     531             :             try {
     532           0 :                 auto const vars = fields.at("py_search_paths");
     533           0 :                 for (const auto &var : vars) {
     534             :                     try {
     535           0 :                         PluginManager::addToPythonPath(state, PluginManager::sanitizedPath(fs::path{var.at("search_path").get<std::string>()}), true);
     536           0 :                     } catch (nlohmann::json::out_of_range &e) {
     537             :                         // empty entry
     538             :                     }
     539             :                 }
     540           0 :             } catch (nlohmann::json::out_of_range &e) {
     541             :                 // catch when no paths are passed
     542             :                 // nothing to do here
     543             :             }
     544             :         }
     545             :     } else {
     546             :         // if no search path objects exist, we still need to do the default searching
     547         771 :         PluginManager::addToPythonPath(state, ".", false);
     548        1542 :         fs::path sanitizedInputFileDir = PluginManager::sanitizedPath(state.dataStrGlobals->inputDirPath);
     549         771 :         PluginManager::addToPythonPath(state, sanitizedInputFileDir, false);
     550        1542 :         std::string epin_path;
     551         771 :         get_environment_variable("epin", epin_path);
     552        1542 :         fs::path epinPathObject = fs::path(epin_path);
     553         771 :         if (!epinPathObject.empty()) {
     554           0 :             auto epinRootDir = FileSystem::getParentDirectoryPath(fs::path(epinPathObject));
     555           0 :             if (FileSystem::pathExists(epinRootDir)) {
     556           0 :                 fs::path sanitizedEnvInputDir = PluginManager::sanitizedPath(epinRootDir);
     557           0 :                 PluginManager::addToPythonPath(state, sanitizedEnvInputDir, true);
     558             :             }
     559             :         }
     560             :     }
     561             : 
     562             :     // Now read all the actual plugins and interpret them
     563             :     // IMPORTANT -- DO NOT CALL setup() UNTIL ALL INSTANCES ARE DONE
     564        1542 :     std::string const sPlugins = "PythonPlugin:Instance";
     565         771 :     int pluginInstances = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, sPlugins);
     566         771 :     if (pluginInstances > 0) {
     567          42 :         auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(sPlugins);
     568          21 :         if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) {
     569             :             ShowSevereError(state,                                                                                // LCOV_EXCL_LINE
     570             :                             "PythonPlugin:Instance: Somehow getNumObjectsFound was > 0 but epJSON.find found 0"); // LCOV_EXCL_LINE
     571             :         }
     572          21 :         auto &instancesValue = instances.value();
     573          65 :         for (auto instance = instancesValue.begin(); instance != instancesValue.end(); ++instance) {
     574          44 :             auto const &fields = instance.value();
     575          44 :             auto const &thisObjectName = instance.key();
     576          44 :             state.dataInputProcessing->inputProcessor->markObjectAsUsed(sPlugins, thisObjectName);
     577          88 :             fs::path modulePath(fields.at("python_module_name").get<std::string>());
     578          88 :             std::string className = fields.at("plugin_class_name").get<std::string>();
     579          88 :             std::string sWarmup = EnergyPlus::UtilityRoutines::MakeUPPERCase(fields.at("run_during_warmup_days").get<std::string>());
     580          44 :             bool warmup = false;
     581          44 :             if (sWarmup == "YES") {
     582          43 :                 warmup = true;
     583             :             }
     584          44 :             state.dataPluginManager->plugins.emplace_back(modulePath, className, thisObjectName, warmup);
     585             :         }
     586             :     }
     587             : 
     588             :     // IMPORTANT - CALL setup() HERE ONCE ALL INSTANCES ARE CONSTRUCTED TO AVOID DESTRUCTOR/MEMORY ISSUES DURING VECTOR RESIZING
     589         815 :     for (auto &plugin : state.dataPluginManager->plugins) {
     590          44 :         plugin.setup(state);
     591             :     }
     592             : 
     593        1542 :     std::string const sGlobals = "PythonPlugin:Variables";
     594         771 :     int globalVarInstances = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, sGlobals);
     595         771 :     if (globalVarInstances > 0) {
     596          30 :         auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(sGlobals);
     597          15 :         if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) {
     598             :             ShowSevereError(state, sGlobals + ": Somehow getNumObjectsFound was > 0 but epJSON.find found 0"); // LCOV_EXCL_LINE
     599             :         }
     600          30 :         std::set<std::string> uniqueNames;
     601          15 :         auto &instancesValue = instances.value();
     602          31 :         for (auto instance = instancesValue.begin(); instance != instancesValue.end(); ++instance) {
     603          16 :             auto const &fields = instance.value();
     604          16 :             auto const &thisObjectName = instance.key();
     605          16 :             state.dataInputProcessing->inputProcessor->markObjectAsUsed(sGlobals, thisObjectName);
     606          32 :             auto const vars = fields.at("global_py_vars");
     607          47 :             for (const auto &var : vars) {
     608          62 :                 std::string const varNameToAdd = var.at("variable_name").get<std::string>();
     609          31 :                 if (uniqueNames.find(varNameToAdd) == uniqueNames.end()) {
     610          31 :                     this->addGlobalVariable(state, varNameToAdd);
     611          31 :                     uniqueNames.insert(varNameToAdd);
     612             :                 } else {
     613           0 :                     ShowWarningMessage(state,
     614           0 :                                        format("Found duplicate variable name in PythonPLugin:Variables objects, ignoring: \"{}\"", varNameToAdd));
     615             :                 }
     616             :             }
     617             :         }
     618             :     }
     619             : 
     620             :     // PythonPlugin:TrendVariable,
     621             :     //       \memo This object sets up a Python plugin trend variable from an Python plugin variable
     622             :     //       \memo A trend variable logs values across timesteps
     623             :     //       \min-fields 3
     624             :     //  A1 , \field Name
     625             :     //       \required-field
     626             :     //       \type alpha
     627             :     //  A2 , \field Name of a Python Plugin Variable
     628             :     //       \required-field
     629             :     //       \type alpha
     630             :     //  N1 ; \field Number of Timesteps to be Logged
     631             :     //       \required-field
     632             :     //       \type integer
     633             :     //       \minimum 1
     634        1542 :     std::string const sTrends = "PythonPlugin:TrendVariable";
     635         771 :     int trendInstances = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, sTrends);
     636         771 :     if (trendInstances > 0) {
     637           6 :         auto const instances = state.dataInputProcessing->inputProcessor->epJSON.find(sTrends);
     638           3 :         if (instances == state.dataInputProcessing->inputProcessor->epJSON.end()) {
     639             :             ShowSevereError(state, sTrends + ": Somehow getNumObjectsFound was > 0 but epJSON.find found 0"); // LCOV_EXCL_LINE
     640             :         }
     641           3 :         auto &instancesValue = instances.value();
     642           7 :         for (auto instance = instancesValue.begin(); instance != instancesValue.end(); ++instance) {
     643           4 :             auto const &fields = instance.value();
     644           8 :             auto const &thisObjectName = EnergyPlus::UtilityRoutines::MakeUPPERCase(instance.key());
     645           4 :             state.dataInputProcessing->inputProcessor->markObjectAsUsed(sGlobals, thisObjectName);
     646           8 :             std::string variableName = fields.at("name_of_a_python_plugin_variable").get<std::string>();
     647           4 :             int variableIndex = EnergyPlus::PluginManagement::PluginManager::getGlobalVariableHandle(state, variableName);
     648           4 :             int numValues = fields.at("number_of_timesteps_to_be_logged").get<int>();
     649           4 :             state.dataPluginManager->trends.emplace_back(state, thisObjectName, numValues, variableIndex);
     650           4 :             this->maxTrendVariableIndex++;
     651             :         }
     652             :     }
     653             : 
     654             :     // Release the global interpreter lock
     655         771 :     PyGILState_Release(gil);
     656             :     // setting up output variables deferred until later in the simulation setup process
     657             : #else
     658             :     // need to alert only if a plugin instance is found
     659             :     std::string const sPlugins = "PythonPlugin:Instance";
     660             :     int pluginInstances = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, sPlugins);
     661             :     if (pluginInstances > 0) {
     662             :         EnergyPlus::ShowFatalError(state, "Python Plugin instance found, but this build of EnergyPlus is not compiled with Python.");
     663             :     }
     664             : #endif
     665         771 : }
     666             : 
     667        1542 : PluginManager::~PluginManager()
     668             : {
     669             : #if LINK_WITH_PYTHON
     670         771 :     if (!this->eplusRunningViaPythonAPI) {
     671         771 :         bool alreadyInitialized = (Py_IsInitialized() != 0);
     672         771 :         if (alreadyInitialized) {
     673         771 :             if (Py_FinalizeEx() < 0) {
     674           0 :                 exit(120);
     675             :             }
     676             :         }
     677             :     }
     678             : #endif // LINK_WITH_PYTHON
     679         771 : }
     680             : 
     681             : #if LINK_WITH_PYTHON
     682        3084 : fs::path PluginManager::sanitizedPath(fs::path const &path)
     683             : {
     684             :     // there are parts of this program that need to write out a string to execute in Python
     685             :     // because of that, escaped backslashes actually need double escaping
     686             :     // plus, the string cannot end with a backslash
     687             :     // sanitize the path to remove any trailing backslash
     688        3084 :     if (path.empty()) {
     689             :         // this is really only likely to occur during unit testing, just return the original blank path
     690           0 :         return path;
     691             :     }
     692        6168 :     std::string pathStr = path.string();
     693        3084 :     if (pathStr.back() == '\\') {
     694           0 :         pathStr.erase(pathStr.size() - 1);
     695             :     }
     696             :     // then sanitize it to escape the backslashes for writing the string literal to Python
     697        6168 :     std::string sanitizedDir;
     698      153400 :     for (char i : pathStr) {
     699      150316 :         if (i == '\\') {
     700           0 :             sanitizedDir += "\\\\";
     701             :         } else {
     702      150316 :             sanitizedDir += i;
     703             :         }
     704             :     }
     705        3084 :     return fs::path(sanitizedDir);
     706             : }
     707             : #else
     708             : fs::path PluginManager::sanitizedPath([[maybe_unused]] fs::path const &path)
     709             : {
     710             :     return fs::path();
     711             : }
     712             : #endif
     713             : 
     714           0 : void PluginInstance::reportPythonError([[maybe_unused]] EnergyPlusData &state)
     715             : {
     716             : #if LINK_WITH_PYTHON
     717           0 :     PyObject *exc_type = nullptr;
     718           0 :     PyObject *exc_value = nullptr;
     719           0 :     PyObject *exc_tb = nullptr;
     720           0 :     PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
     721             :     // Normalizing the exception is needed. Without it, our custom EnergyPlusException go through just fine
     722             :     // but any ctypes built-in exception for eg will have wrong types
     723           0 :     PyErr_NormalizeException(&exc_type, &exc_value, &exc_tb);
     724           0 :     PyObject *str_exc_value = PyObject_Repr(exc_value); // Now a unicode object
     725           0 :     PyObject *pyStr2 = PyUnicode_AsEncodedString(str_exc_value, "utf-8", "Error ~");
     726           0 :     Py_DECREF(str_exc_value);
     727           0 :     char *strExcValue = PyBytes_AsString(pyStr2); // NOLINT(hicpp-signed-bitwise)
     728           0 :     Py_DECREF(pyStr2);
     729           0 :     EnergyPlus::ShowContinueError(state, "Python error description follows: ");
     730           0 :     EnergyPlus::ShowContinueError(state, strExcValue);
     731             : 
     732             :     // See if we can get a full traceback.
     733             :     // Calls into python, and does the same as capturing the exception in `e`
     734             :     // then `print(traceback.format_exception(e.type, e.value, e.tb))`
     735           0 :     PyObject *pModuleName = PyUnicode_DecodeFSDefault("traceback");
     736           0 :     PyObject *pyth_module = PyImport_Import(pModuleName);
     737           0 :     Py_DECREF(pModuleName);
     738             : 
     739           0 :     if (pyth_module == nullptr) {
     740           0 :         EnergyPlus::ShowContinueError(state, "Cannot find 'traceback' module in reportPythonError(), this is weird");
     741           0 :         return;
     742             :     }
     743             : 
     744           0 :     PyObject *pyth_func = PyObject_GetAttrString(pyth_module, "format_exception");
     745           0 :     Py_DECREF(pyth_module); // PyImport_Import returns a new reference, decrement it
     746             : 
     747           0 :     if (pyth_func || PyCallable_Check(pyth_func)) {
     748             : 
     749           0 :         PyObject *pyth_val = PyObject_CallFunction(pyth_func, "OOO", exc_type, exc_value, exc_tb);
     750             : 
     751             :         // traceback.format_exception returns a list, so iterate on that
     752           0 :         if (!pyth_val || !PyList_Check(pyth_val)) { // NOLINT(hicpp-signed-bitwise)
     753           0 :             EnergyPlus::ShowContinueError(state, "In reportPythonError(), traceback.format_exception did not return a list.");
     754           0 :             return;
     755             :         }
     756             : 
     757           0 :         unsigned long numVals = PyList_Size(pyth_val);
     758           0 :         if (numVals == 0) {
     759           0 :             EnergyPlus::ShowContinueError(state, "No traceback available");
     760           0 :             return;
     761             :         }
     762             : 
     763           0 :         EnergyPlus::ShowContinueError(state, "Python traceback follows: ");
     764             : 
     765           0 :         EnergyPlus::ShowContinueError(state, "```");
     766             : 
     767           0 :         for (unsigned long itemNum = 0; itemNum < numVals; itemNum++) {
     768           0 :             PyObject *item = PyList_GetItem(pyth_val, itemNum);
     769           0 :             if (PyUnicode_Check(item)) { // NOLINT(hicpp-signed-bitwise) -- something inside Python code causes warning
     770           0 :                 std::string traceback_line = PyUnicode_AsUTF8(item);
     771           0 :                 if (!traceback_line.empty() && traceback_line[traceback_line.length() - 1] == '\n') {
     772           0 :                     traceback_line.erase(traceback_line.length() - 1);
     773             :                 }
     774           0 :                 EnergyPlus::ShowContinueError(state, " >>> " + traceback_line);
     775             :             }
     776             :             // PyList_GetItem returns a borrowed reference, do not decrement
     777             :         }
     778             : 
     779           0 :         EnergyPlus::ShowContinueError(state, "```");
     780             : 
     781             :         // PyList_Size returns a borrowed reference, do not decrement
     782           0 :         Py_DECREF(pyth_val); // PyObject_CallFunction returns new reference, decrement
     783             :     }
     784           0 :     Py_DECREF(pyth_func); // PyObject_GetAttrString returns a new reference, decrement it
     785             : #endif
     786             : }
     787             : 
     788          44 : void PluginInstance::setup([[maybe_unused]] EnergyPlusData &state)
     789             : {
     790             : #if LINK_WITH_PYTHON
     791             :     // this first section is really all about just ultimately getting a full Python class instance
     792             :     // this answer helped with a few things: https://ru.stackoverflow.com/a/785927
     793             : 
     794          44 :     PyObject *pModuleName = PyUnicode_DecodeFSDefault(this->modulePath.string().c_str());
     795          44 :     this->pModule = PyImport_Import(pModuleName);
     796             :     // PyUnicode_DecodeFSDefault documentation does not explicitly say whether it returns a new or borrowed reference,
     797             :     // but other functions in that section say they return a new reference, and that makes sense to me, so I think we
     798             :     // should decrement it.
     799          44 :     Py_DECREF(pModuleName);
     800          44 :     if (!this->pModule) {
     801           0 :         EnergyPlus::ShowSevereError(state, "Failed to import module \"" + this->modulePath.string() + "\"");
     802             :         // ONLY call PyErr_Print if PyErr has occurred, otherwise it will cause other problems
     803           0 :         if (PyErr_Occurred()) {
     804           0 :             PluginInstance::reportPythonError(state);
     805             :         } else {
     806           0 :             EnergyPlus::ShowContinueError(state, "It could be that the module could not be found, or that there was an error in importing");
     807             :         }
     808           0 :         EnergyPlus::ShowFatalError(state, "Python import error causes program termination");
     809             :     }
     810          44 :     PyObject *pModuleDict = PyModule_GetDict(this->pModule);
     811          44 :     if (!pModuleDict) {
     812           0 :         EnergyPlus::ShowSevereError(state, "Failed to read module dictionary from module \"" + this->modulePath.string() + "\"");
     813           0 :         if (PyErr_Occurred()) {
     814           0 :             PluginInstance::reportPythonError(state);
     815             :         } else {
     816           0 :             EnergyPlus::ShowContinueError(state, "It could be that the module was empty");
     817             :         }
     818           0 :         EnergyPlus::ShowFatalError(state, "Python module error causes program termination");
     819             :     }
     820          88 :     std::string fileVarName = "__file__";
     821          44 :     PyObject *pFullPath = PyDict_GetItemString(pModuleDict, fileVarName.c_str());
     822          44 :     if (!pFullPath) {
     823             :         // something went really wrong, this should only happen if you do some *weird* python stuff like
     824             :         // import from database or something
     825           0 :         ShowFatalError(state, "Could not get full path");
     826             :     } else {
     827          44 :         PyObject *pStrObj = PyUnicode_AsUTF8String(pFullPath);
     828          44 :         char *zStr = PyBytes_AsString(pStrObj);
     829          88 :         std::string s(zStr);
     830          44 :         Py_DECREF(pStrObj); // PyUnicode_AsUTF8String returns a new reference, decrement it
     831          44 :         ShowMessage(state, "PythonPlugin: Class " + className + " imported from: " + s);
     832             :     }
     833          44 :     PyObject *pClass = PyDict_GetItemString(pModuleDict, className.c_str());
     834             :     // Py_DECREF(pModuleDict);  // PyModule_GetDict returns a borrowed reference, DO NOT decrement
     835          44 :     if (!pClass) {
     836           0 :         EnergyPlus::ShowSevereError(state, "Failed to get class type \"" + className + "\" from module \"" + modulePath.string() + "\"");
     837           0 :         if (PyErr_Occurred()) {
     838           0 :             PluginInstance::reportPythonError(state);
     839             :         } else {
     840           0 :             EnergyPlus::ShowContinueError(state, "It could be the class name is misspelled or missing.");
     841             :         }
     842           0 :         EnergyPlus::ShowFatalError(state, "Python class import error causes program termination");
     843             :     }
     844          44 :     if (!PyCallable_Check(pClass)) {
     845           0 :         EnergyPlus::ShowSevereError(state, "Got class type \"" + className + "\", but it cannot be called/instantiated");
     846           0 :         if (PyErr_Occurred()) {
     847           0 :             PluginInstance::reportPythonError(state);
     848             :         } else {
     849           0 :             EnergyPlus::ShowContinueError(state, "Is it possible the class name is actually just a variable?");
     850             :         }
     851           0 :         EnergyPlus::ShowFatalError(state, "Python class check error causes program termination");
     852             :     }
     853          44 :     this->pClassInstance = PyObject_CallObject(pClass, nullptr);
     854             :     // Py_DECREF(pClass);  // PyDict_GetItemString returns a borrowed reference, DO NOT decrement
     855          44 :     if (!this->pClassInstance) {
     856           0 :         EnergyPlus::ShowSevereError(state, "Something went awry calling class constructor for class \"" + className + "\"");
     857           0 :         if (PyErr_Occurred()) {
     858           0 :             PluginInstance::reportPythonError(state);
     859             :         } else {
     860           0 :             EnergyPlus::ShowContinueError(state, "It is possible the plugin class constructor takes extra arguments - it shouldn't.");
     861             :         }
     862           0 :         EnergyPlus::ShowFatalError(state, "Python class constructor error causes program termination");
     863             :     }
     864             :     // PyObject_CallObject returns a new reference, that we need to manage
     865             :     // I think we need to keep it around in memory though so the class methods can be called later on,
     866             :     // so I don't intend on decrementing it, at least not until the manager destructor
     867             :     // In any case, it will be an **extremely** tiny memory use if we hold onto it a bit too long
     868             : 
     869             :     // check which methods are overridden in the derived class
     870          88 :     std::string const detectOverriddenFunctionName = "_detect_overridden";
     871          44 :     PyObject *detectFunction = PyObject_GetAttrString(this->pClassInstance, detectOverriddenFunctionName.c_str());
     872          44 :     if (!detectFunction || !PyCallable_Check(detectFunction)) {
     873           0 :         EnergyPlus::ShowSevereError(state,
     874           0 :                                     "Could not find or call function \"" + detectOverriddenFunctionName + "\" on class \"" +
     875           0 :                                         this->modulePath.string() + "." + this->className + "\"");
     876           0 :         if (PyErr_Occurred()) {
     877           0 :             PluginInstance::reportPythonError(state);
     878             :         } else {
     879           0 :             EnergyPlus::ShowContinueError(state, "This function should be available on the base class, so this is strange.");
     880             :         }
     881           0 :         EnergyPlus::ShowFatalError(state, "Python _detect_overridden() function error causes program termination");
     882             :     }
     883          44 :     PyObject *pFunctionResponse = PyObject_CallFunction(detectFunction, nullptr);
     884          44 :     Py_DECREF(detectFunction); // PyObject_GetAttrString returns a new reference, decrement it
     885          44 :     if (!pFunctionResponse) {
     886           0 :         EnergyPlus::ShowSevereError(state, "Call to _detect_overridden() on " + this->stringIdentifier + " failed!");
     887           0 :         if (PyErr_Occurred()) {
     888           0 :             PluginInstance::reportPythonError(state);
     889             :         } else {
     890           0 :             EnergyPlus::ShowContinueError(state, "This is available on the base class and should not be overridden...strange.");
     891             :         }
     892           0 :         EnergyPlus::ShowFatalError(state, "Program terminates after call to _detect_overridden() on " + this->stringIdentifier + " failed!");
     893             :     }
     894          44 :     if (!PyList_Check(pFunctionResponse)) { // NOLINT(hicpp-signed-bitwise)
     895           0 :         EnergyPlus::ShowFatalError(state, "Invalid return from _detect_overridden() on class \"" + this->stringIdentifier + ", this is weird");
     896             :     }
     897          44 :     unsigned long numVals = PyList_Size(pFunctionResponse);
     898             :     // at this point we know which base class methods are being overridden by the derived class
     899             :     // we can loop over them and based on the name check the appropriate flag and assign the function pointer
     900          44 :     if (numVals == 0) {
     901           0 :         EnergyPlus::ShowFatalError(
     902           0 :             state, "Python plugin \"" + this->stringIdentifier + "\" did not override any base class methods; must override at least one");
     903             :     }
     904          89 :     for (unsigned long itemNum = 0; itemNum < numVals; itemNum++) {
     905          45 :         PyObject *item = PyList_GetItem(pFunctionResponse, itemNum);
     906          45 :         if (PyUnicode_Check(item)) { // NOLINT(hicpp-signed-bitwise) -- something inside Python code causes warning
     907          90 :             std::string functionName = PyUnicode_AsUTF8(item);
     908          45 :             if (functionName == this->sHookBeginNewEnvironment) {
     909           1 :                 this->bHasBeginNewEnvironment = true;
     910           1 :                 this->pBeginNewEnvironment = PyUnicode_FromString(functionName.c_str());
     911          44 :             } else if (functionName == this->sHookBeginZoneTimestepBeforeSetCurrentWeather) {
     912           1 :                 this->bHasBeginZoneTimestepBeforeSetCurrentWeather = true;
     913           1 :                 this->pBeginZoneTimestepBeforeSetCurrentWeather = PyUnicode_FromString(functionName.c_str());
     914          43 :             } else if (functionName == this->sHookAfterNewEnvironmentWarmUpIsComplete) {
     915           0 :                 this->bHasAfterNewEnvironmentWarmUpIsComplete = true;
     916           0 :                 this->pAfterNewEnvironmentWarmUpIsComplete = PyUnicode_FromString(functionName.c_str());
     917          43 :             } else if (functionName == this->sHookBeginZoneTimestepBeforeInitHeatBalance) {
     918           1 :                 this->bHasBeginZoneTimestepBeforeInitHeatBalance = true;
     919           1 :                 this->pBeginZoneTimestepBeforeInitHeatBalance = PyUnicode_FromString(functionName.c_str());
     920          42 :             } else if (functionName == this->sHookBeginZoneTimestepAfterInitHeatBalance) {
     921           0 :                 this->bHasBeginZoneTimestepAfterInitHeatBalance = true;
     922           0 :                 this->pBeginZoneTimestepAfterInitHeatBalance = PyUnicode_FromString(functionName.c_str());
     923          42 :             } else if (functionName == this->sHookBeginTimestepBeforePredictor) {
     924           9 :                 this->bHasBeginTimestepBeforePredictor = true;
     925           9 :                 this->pBeginTimestepBeforePredictor = PyUnicode_FromString(functionName.c_str());
     926          33 :             } else if (functionName == this->sHookAfterPredictorBeforeHVACManagers) {
     927           0 :                 this->bHasAfterPredictorBeforeHVACManagers = true;
     928           0 :                 this->pAfterPredictorBeforeHVACManagers = PyUnicode_FromString(functionName.c_str());
     929          33 :             } else if (functionName == this->sHookAfterPredictorAfterHVACManagers) {
     930          17 :                 this->bHasAfterPredictorAfterHVACManagers = true;
     931          17 :                 this->pAfterPredictorAfterHVACManagers = PyUnicode_FromString(functionName.c_str());
     932          16 :             } else if (functionName == this->sHookInsideHVACSystemIterationLoop) {
     933           4 :                 this->bHasInsideHVACSystemIterationLoop = true;
     934           4 :                 this->pInsideHVACSystemIterationLoop = PyUnicode_FromString(functionName.c_str());
     935          12 :             } else if (functionName == this->sHookEndOfZoneTimestepBeforeZoneReporting) {
     936           5 :                 this->bHasEndOfZoneTimestepBeforeZoneReporting = true;
     937           5 :                 this->pEndOfZoneTimestepBeforeZoneReporting = PyUnicode_FromString(functionName.c_str());
     938           7 :             } else if (functionName == this->sHookEndOfZoneTimestepAfterZoneReporting) {
     939           0 :                 this->bHasEndOfZoneTimestepAfterZoneReporting = true;
     940           0 :                 this->pEndOfZoneTimestepAfterZoneReporting = PyUnicode_FromString(functionName.c_str());
     941           7 :             } else if (functionName == this->sHookEndOfSystemTimestepBeforeHVACReporting) {
     942           0 :                 this->bHasEndOfSystemTimestepBeforeHVACReporting = true;
     943           0 :                 this->pEndOfSystemTimestepBeforeHVACReporting = PyUnicode_FromString(functionName.c_str());
     944           7 :             } else if (functionName == this->sHookEndOfSystemTimestepAfterHVACReporting) {
     945           0 :                 this->bHasEndOfSystemTimestepAfterHVACReporting = true;
     946           0 :                 this->pEndOfSystemTimestepAfterHVACReporting = PyUnicode_FromString(functionName.c_str());
     947           7 :             } else if (functionName == this->sHookEndOfZoneSizing) {
     948           0 :                 this->bHasEndOfZoneSizing = true;
     949           0 :                 this->pEndOfZoneSizing = PyUnicode_FromString(functionName.c_str());
     950           7 :             } else if (functionName == this->sHookEndOfSystemSizing) {
     951           1 :                 this->bHasEndOfSystemSizing = true;
     952           1 :                 this->pEndOfSystemSizing = PyUnicode_FromString(functionName.c_str());
     953           6 :             } else if (functionName == this->sHookAfterComponentInputReadIn) {
     954           0 :                 this->bHasAfterComponentInputReadIn = true;
     955           0 :                 this->pAfterComponentInputReadIn = PyUnicode_FromString(functionName.c_str());
     956           6 :             } else if (functionName == this->sHookUserDefinedComponentModel) {
     957           6 :                 this->bHasUserDefinedComponentModel = true;
     958           6 :                 this->pUserDefinedComponentModel = PyUnicode_FromString(functionName.c_str());
     959           0 :             } else if (functionName == this->sHookUnitarySystemSizing) {
     960           0 :                 this->bHasUnitarySystemSizing = true;
     961           0 :                 this->pUnitarySystemSizing = PyUnicode_FromString(functionName.c_str());
     962             :             } else {
     963             :                 // the Python _detect_function worker is supposed to ignore any other functions so they don't show up at this point
     964             :                 // I don't think it's appropriate to warn here, so just ignore and move on
     965             :             }
     966             :         }
     967             :         // PyList_GetItem returns a borrowed reference, do not decrement
     968             :     }
     969             :     // PyList_Size returns a borrowed reference, do not decrement
     970          44 :     Py_DECREF(pFunctionResponse); // PyObject_CallFunction returns new reference, decrement
     971             : #endif
     972          44 : }
     973             : 
     974           0 : void PluginInstance::shutdown() const
     975             : {
     976             : #if LINK_WITH_PYTHON
     977           0 :     Py_DECREF(this->pClassInstance);
     978           0 :     Py_DECREF(this->pModule); // PyImport_Import returns a new reference, decrement it
     979           0 :     if (this->bHasBeginNewEnvironment) Py_DECREF(this->pBeginNewEnvironment);
     980           0 :     if (this->bHasAfterNewEnvironmentWarmUpIsComplete) Py_DECREF(this->pAfterNewEnvironmentWarmUpIsComplete);
     981           0 :     if (this->bHasBeginZoneTimestepBeforeInitHeatBalance) Py_DECREF(this->pBeginZoneTimestepBeforeInitHeatBalance);
     982           0 :     if (this->bHasBeginZoneTimestepAfterInitHeatBalance) Py_DECREF(this->pBeginZoneTimestepAfterInitHeatBalance);
     983           0 :     if (this->bHasBeginTimestepBeforePredictor) Py_DECREF(this->pBeginTimestepBeforePredictor);
     984           0 :     if (this->bHasAfterPredictorBeforeHVACManagers) Py_DECREF(this->pAfterPredictorBeforeHVACManagers);
     985           0 :     if (this->bHasAfterPredictorAfterHVACManagers) Py_DECREF(this->pAfterPredictorAfterHVACManagers);
     986           0 :     if (this->bHasInsideHVACSystemIterationLoop) Py_DECREF(this->pInsideHVACSystemIterationLoop);
     987           0 :     if (this->bHasEndOfZoneTimestepBeforeZoneReporting) Py_DECREF(this->pEndOfZoneTimestepBeforeZoneReporting);
     988           0 :     if (this->bHasEndOfZoneTimestepAfterZoneReporting) Py_DECREF(this->pEndOfZoneTimestepAfterZoneReporting);
     989           0 :     if (this->bHasEndOfSystemTimestepBeforeHVACReporting) Py_DECREF(this->pEndOfSystemTimestepBeforeHVACReporting);
     990           0 :     if (this->bHasEndOfSystemTimestepAfterHVACReporting) Py_DECREF(this->pEndOfSystemTimestepAfterHVACReporting);
     991           0 :     if (this->bHasEndOfZoneSizing) Py_DECREF(this->pEndOfZoneSizing);
     992           0 :     if (this->bHasEndOfSystemSizing) Py_DECREF(this->pEndOfSystemSizing);
     993           0 :     if (this->bHasAfterComponentInputReadIn) Py_DECREF(this->pAfterComponentInputReadIn);
     994           0 :     if (this->bHasUserDefinedComponentModel) Py_DECREF(this->pUserDefinedComponentModel);
     995           0 :     if (this->bHasUnitarySystemSizing) Py_DECREF(this->pUnitarySystemSizing);
     996             : #endif
     997           0 : }
     998             : 
     999             : #if LINK_WITH_PYTHON
    1000     3048873 : bool PluginInstance::run(EnergyPlusData &state, EMSManager::EMSCallFrom iCalledFrom) const
    1001             : {
    1002             :     // returns true if a plugin actually ran
    1003     3048873 :     PyObject *pFunctionName = nullptr;
    1004     3048873 :     const char *functionName = nullptr;
    1005     3048873 :     if (iCalledFrom == EMSManager::EMSCallFrom::BeginNewEnvironment) {
    1006          86 :         if (this->bHasBeginNewEnvironment) {
    1007           2 :             pFunctionName = this->pBeginNewEnvironment;
    1008           2 :             functionName = this->sHookBeginNewEnvironment;
    1009             :         }
    1010     3048787 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::BeginZoneTimestepBeforeSetCurrentWeather) {
    1011      160992 :         if (this->bHasBeginZoneTimestepBeforeSetCurrentWeather) {
    1012        2160 :             pFunctionName = this->pBeginZoneTimestepBeforeSetCurrentWeather;
    1013        2160 :             functionName = this->sHookBeginZoneTimestepBeforeSetCurrentWeather;
    1014             :         }
    1015     2887795 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::ZoneSizing) {
    1016          37 :         if (this->bHasEndOfZoneSizing) {
    1017           0 :             pFunctionName = this->pEndOfZoneSizing;
    1018           0 :             functionName = this->sHookEndOfZoneSizing;
    1019             :         }
    1020     2887758 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::SystemSizing) {
    1021          34 :         if (this->bHasEndOfSystemSizing) {
    1022           1 :             pFunctionName = this->pEndOfSystemSizing;
    1023           1 :             functionName = this->sHookEndOfSystemSizing;
    1024             :         }
    1025     2887724 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::BeginNewEnvironmentAfterWarmUp) {
    1026         160 :         if (this->bHasAfterNewEnvironmentWarmUpIsComplete) {
    1027           0 :             pFunctionName = this->pAfterNewEnvironmentWarmUpIsComplete;
    1028           0 :             functionName = this->sHookAfterNewEnvironmentWarmUpIsComplete;
    1029             :         }
    1030     2887564 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::BeginTimestepBeforePredictor) {
    1031      219288 :         if (this->bHasBeginTimestepBeforePredictor) {
    1032       67422 :             pFunctionName = this->pBeginTimestepBeforePredictor;
    1033       67422 :             functionName = this->sHookBeginTimestepBeforePredictor;
    1034             :         }
    1035     2668276 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::BeforeHVACManagers) {
    1036      234049 :         if (this->bHasAfterPredictorBeforeHVACManagers) {
    1037           0 :             pFunctionName = this->pAfterPredictorBeforeHVACManagers;
    1038           0 :             functionName = this->sHookAfterPredictorBeforeHVACManagers;
    1039             :         }
    1040     2434227 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::AfterHVACManagers) {
    1041      234049 :         if (this->bHasAfterPredictorAfterHVACManagers) {
    1042       59134 :             pFunctionName = this->pAfterPredictorAfterHVACManagers;
    1043       59134 :             functionName = this->sHookAfterPredictorAfterHVACManagers;
    1044             :         }
    1045     2200178 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::HVACIterationLoop) {
    1046      537480 :         if (this->bHasInsideHVACSystemIterationLoop) {
    1047      137176 :             pFunctionName = this->pInsideHVACSystemIterationLoop;
    1048      137176 :             functionName = this->sHookInsideHVACSystemIterationLoop;
    1049             :         }
    1050     1662698 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::EndSystemTimestepBeforeHVACReporting) {
    1051      285936 :         if (this->bHasEndOfSystemTimestepBeforeHVACReporting) {
    1052           0 :             pFunctionName = this->pEndOfSystemTimestepBeforeHVACReporting;
    1053           0 :             functionName = this->sHookEndOfSystemTimestepBeforeHVACReporting;
    1054             :         }
    1055     1376762 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::EndSystemTimestepAfterHVACReporting) {
    1056      285936 :         if (this->bHasEndOfSystemTimestepAfterHVACReporting) {
    1057           0 :             pFunctionName = this->pEndOfSystemTimestepAfterHVACReporting;
    1058           0 :             functionName = this->sHookEndOfSystemTimestepAfterHVACReporting;
    1059             :         }
    1060     1090826 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::EndZoneTimestepBeforeZoneReporting) {
    1061      219288 :         if (this->bHasEndOfZoneTimestepBeforeZoneReporting) {
    1062       15528 :             pFunctionName = this->pEndOfZoneTimestepBeforeZoneReporting;
    1063       15528 :             functionName = this->sHookEndOfZoneTimestepBeforeZoneReporting;
    1064             :         }
    1065      871538 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::EndZoneTimestepAfterZoneReporting) {
    1066      219288 :         if (this->bHasEndOfZoneTimestepAfterZoneReporting) {
    1067           0 :             pFunctionName = this->pEndOfZoneTimestepAfterZoneReporting;
    1068           0 :             functionName = this->sHookEndOfZoneTimestepAfterZoneReporting;
    1069             :         }
    1070      652250 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::ComponentGetInput) {
    1071          43 :         if (this->bHasAfterComponentInputReadIn) {
    1072           0 :             pFunctionName = this->pAfterComponentInputReadIn;
    1073           0 :             functionName = this->sHookAfterComponentInputReadIn;
    1074             :         }
    1075      652207 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::UserDefinedComponentModel) {
    1076      213588 :         if (this->bHasUserDefinedComponentModel) {
    1077      213588 :             pFunctionName = this->pUserDefinedComponentModel;
    1078      213588 :             functionName = this->sHookUserDefinedComponentModel;
    1079             :         }
    1080      438619 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::UnitarySystemSizing) {
    1081           0 :         if (this->bHasUnitarySystemSizing) {
    1082           0 :             pFunctionName = this->pUnitarySystemSizing;
    1083           0 :             functionName = this->sHookUnitarySystemSizing;
    1084             :         }
    1085      438619 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::BeginZoneTimestepBeforeInitHeatBalance) {
    1086      219288 :         if (this->bHasBeginZoneTimestepBeforeInitHeatBalance) {
    1087        4038 :             pFunctionName = this->pBeginZoneTimestepBeforeInitHeatBalance;
    1088        4038 :             functionName = this->sHookBeginZoneTimestepBeforeInitHeatBalance;
    1089             :         }
    1090      219331 :     } else if (iCalledFrom == EMSManager::EMSCallFrom::BeginZoneTimestepAfterInitHeatBalance) {
    1091      219288 :         if (this->bHasBeginZoneTimestepAfterInitHeatBalance) {
    1092           0 :             pFunctionName = this->pBeginZoneTimestepAfterInitHeatBalance;
    1093           0 :             functionName = this->sHookBeginZoneTimestepAfterInitHeatBalance;
    1094             :         }
    1095             :     }
    1096             : 
    1097             :     // leave if we didn't find a match
    1098     3048873 :     if (!pFunctionName) {
    1099     2549824 :         return false;
    1100             :     }
    1101             : 
    1102             :     // Get control of the global interpreter lock
    1103      499049 :     PyGILState_STATE gil = PyGILState_Ensure();
    1104             : 
    1105             :     // then call the main function
    1106             :     // static const PyObject oneArgObjFormat = Py_BuildValue)("O");
    1107      499049 :     PyObject *pStateInstance = PyLong_FromVoidPtr((void *)&state);
    1108      499049 :     PyObject *pFunctionResponse = PyObject_CallMethodObjArgs(this->pClassInstance, pFunctionName, pStateInstance, nullptr);
    1109      499049 :     Py_DECREF(pStateInstance);
    1110      499049 :     if (!pFunctionResponse) {
    1111           0 :         std::string const functionNameAsString(functionName); // only convert to string if an error occurs
    1112           0 :         EnergyPlus::ShowSevereError(state, "Call to " + functionNameAsString + "() on " + this->stringIdentifier + " failed!");
    1113           0 :         if (PyErr_Occurred()) {
    1114           0 :             PluginInstance::reportPythonError(state);
    1115             :         } else {
    1116           0 :             EnergyPlus::ShowContinueError(state, "This could happen for any number of reasons, check the plugin code.");
    1117             :         }
    1118           0 :         PyGILState_Release(gil);
    1119           0 :         EnergyPlus::ShowFatalError(state,
    1120           0 :                                    "Program terminates after call to " + functionNameAsString + "() on " + this->stringIdentifier + " failed!");
    1121             :     }
    1122      499049 :     if (PyLong_Check(pFunctionResponse)) { // NOLINT(hicpp-signed-bitwise)
    1123      499049 :         auto exitCode = PyLong_AsLong(pFunctionResponse);
    1124      499049 :         if (exitCode == 0) {
    1125             :             // success
    1126           0 :         } else if (exitCode == 1) {
    1127           0 :             PyGILState_Release(gil);
    1128           0 :             EnergyPlus::ShowFatalError(state, "Python Plugin \"" + this->stringIdentifier + "\" returned 1 to indicate EnergyPlus should abort");
    1129             :         }
    1130             :     } else {
    1131           0 :         std::string const functionNameAsString(functionName); // only convert to string if an error occurs
    1132           0 :         PyGILState_Release(gil);
    1133           0 :         EnergyPlus::ShowFatalError(state,
    1134           0 :                                    "Invalid return from " + functionNameAsString + "() on class \"" + this->stringIdentifier +
    1135             :                                        ", make sure it returns an integer exit code, either zero (success) or one (failure)");
    1136             :     }
    1137      499049 :     Py_DECREF(pFunctionResponse); // PyObject_CallFunction returns new reference, decrement
    1138      499049 :     if (state.dataPluginManager->apiErrorFlag) {
    1139           0 :         PyGILState_Release(gil);
    1140           0 :         EnergyPlus::ShowFatalError(state, "API problems encountered while running plugin cause program termination.");
    1141             :     }
    1142      499049 :     PyGILState_Release(gil);
    1143      499049 :     return true;
    1144             : }
    1145             : #else
    1146             : bool PluginInstance::run([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] EMSManager::EMSCallFrom iCalledFrom) const
    1147             : {
    1148             :     return false;
    1149             : }
    1150             : #endif
    1151             : 
    1152             : #if LINK_WITH_PYTHON
    1153        4626 : void PluginManager::addToPythonPath(EnergyPlusData &state, const fs::path &path, bool userDefinedPath)
    1154             : {
    1155        4626 :     if (path.empty()) return;
    1156             : 
    1157        9252 :     std::string command = "sys.path.insert(0, \"" + path.string() + "\")";
    1158        4626 :     if (PyRun_SimpleString(command.c_str()) == 0) {
    1159        4626 :         if (userDefinedPath) {
    1160           0 :             EnergyPlus::ShowMessage(state, "Successfully added path \"" + path.string() + "\" to the sys.path in Python");
    1161             :         }
    1162             :         // PyRun_SimpleString)("print(' EPS : ' + str(sys.path))");
    1163             :     } else {
    1164           0 :         EnergyPlus::ShowFatalError(state, "ERROR adding \"" + path.string() + "\" to the sys.path in Python");
    1165             :     }
    1166             : }
    1167             : #else
    1168             : void PluginManager::addToPythonPath([[maybe_unused]] EnergyPlusData &state,
    1169             :                                     [[maybe_unused]] const fs::path &path,
    1170             :                                     [[maybe_unused]] bool userDefinedPath)
    1171             : {
    1172             : }
    1173             : #endif
    1174             : 
    1175             : #if LINK_WITH_PYTHON
    1176          31 : void PluginManager::addGlobalVariable(EnergyPlusData &state, const std::string &name)
    1177             : {
    1178          62 :     std::string const varNameUC = EnergyPlus::UtilityRoutines::MakeUPPERCase(name);
    1179          31 :     state.dataPluginManager->globalVariableNames.push_back(varNameUC);
    1180          31 :     state.dataPluginManager->globalVariableValues.push_back(Real64());
    1181          31 :     this->maxGlobalVariableIndex++;
    1182          31 : }
    1183             : #else
    1184             : void PluginManager::addGlobalVariable([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] const std::string &name)
    1185             : {
    1186             : }
    1187             : #endif
    1188             : 
    1189             : #if LINK_WITH_PYTHON
    1190          51 : int PluginManager::getGlobalVariableHandle(EnergyPlusData &state, const std::string &name, bool const suppress_warning)
    1191             : { // note zero is a valid handle
    1192         102 :     std::string const varNameUC = EnergyPlus::UtilityRoutines::MakeUPPERCase(name);
    1193         102 :     auto const it = std::find(state.dataPluginManager->globalVariableNames.begin(), state.dataPluginManager->globalVariableNames.end(), varNameUC);
    1194          51 :     if (it != state.dataPluginManager->globalVariableNames.end()) {
    1195          51 :         return std::distance(state.dataPluginManager->globalVariableNames.begin(), it);
    1196             :     } else {
    1197           0 :         if (suppress_warning) {
    1198           0 :             return -1;
    1199             :         } else {
    1200           0 :             EnergyPlus::ShowSevereError(state, "Tried to retrieve handle for a nonexistent plugin global variable");
    1201           0 :             EnergyPlus::ShowContinueError(state, "Name looked up: \"" + varNameUC + "\", available names: ");
    1202           0 :             for (auto const &gvName : state.dataPluginManager->globalVariableNames) {
    1203           0 :                 EnergyPlus::ShowContinueError(state, "    \"" + gvName + "\"");
    1204             :             }
    1205           0 :             EnergyPlus::ShowFatalError(state, "Plugin global variable problem causes program termination");
    1206           0 :             return -1; // hush the compiler warning
    1207             :         }
    1208             :     }
    1209             : }
    1210             : #else
    1211             : int PluginManager::getGlobalVariableHandle([[maybe_unused]] EnergyPlusData &state,
    1212             :                                            [[maybe_unused]] const std::string &name,
    1213             :                                            [[maybe_unused]] bool const suppress_warning)
    1214             : {
    1215             :     return -1;
    1216             : }
    1217             : #endif
    1218             : 
    1219             : #if LINK_WITH_PYTHON
    1220           3 : int PluginManager::getTrendVariableHandle(EnergyPlusData &state, const std::string &name)
    1221             : {
    1222           6 :     std::string const varNameUC = EnergyPlus::UtilityRoutines::MakeUPPERCase(name);
    1223           4 :     for (size_t i = 0; i < state.dataPluginManager->trends.size(); i++) {
    1224           4 :         auto &thisTrend = state.dataPluginManager->trends[i];
    1225           4 :         if (thisTrend.name == varNameUC) {
    1226           3 :             return i;
    1227             :         }
    1228             :     }
    1229           0 :     return -1;
    1230             : }
    1231             : #else
    1232             : int PluginManager::getTrendVariableHandle([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] const std::string &name)
    1233             : {
    1234             :     return -1;
    1235             : }
    1236             : #endif
    1237             : 
    1238             : #if LINK_WITH_PYTHON
    1239        6346 : Real64 PluginManager::getTrendVariableValue(EnergyPlusData &state, int handle, int timeIndex)
    1240             : {
    1241        6346 :     return state.dataPluginManager->trends[handle].values[timeIndex];
    1242             : }
    1243             : #else
    1244             : Real64 PluginManager::getTrendVariableValue([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] int handle, [[maybe_unused]] int timeIndex)
    1245             : {
    1246             :     return 0.0;
    1247             : }
    1248             : #endif
    1249             : 
    1250             : #if LINK_WITH_PYTHON
    1251        2016 : Real64 PluginManager::getTrendVariableAverage(EnergyPlusData &state, int handle, int count)
    1252             : {
    1253        2016 :     Real64 sum = 0;
    1254     2034144 :     for (int i = 0; i < count; i++) {
    1255     2032128 :         sum += state.dataPluginManager->trends[handle].values[i];
    1256             :     }
    1257        2016 :     return sum / count;
    1258             : }
    1259             : #else
    1260             : Real64 PluginManager::getTrendVariableAverage([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] int handle, [[maybe_unused]] int count)
    1261             : {
    1262             :     return 0.0;
    1263             : }
    1264             : #endif
    1265             : 
    1266             : #if LINK_WITH_PYTHON
    1267           0 : Real64 PluginManager::getTrendVariableMin(EnergyPlusData &state, int handle, int count)
    1268             : {
    1269           0 :     Real64 minimumValue = 9999999999999;
    1270           0 :     for (int i = 0; i < count; i++) {
    1271           0 :         if (state.dataPluginManager->trends[handle].values[i] < minimumValue) {
    1272           0 :             minimumValue = state.dataPluginManager->trends[handle].values[i];
    1273             :         }
    1274             :     }
    1275           0 :     return minimumValue;
    1276             : }
    1277             : #else
    1278             : Real64 PluginManager::getTrendVariableMin([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] int handle, [[maybe_unused]] int count)
    1279             : {
    1280             :     return 0.0;
    1281             : }
    1282             : #endif
    1283             : 
    1284             : #if LINK_WITH_PYTHON
    1285           0 : Real64 PluginManager::getTrendVariableMax(EnergyPlusData &state, int handle, int count)
    1286             : {
    1287           0 :     Real64 maximumValue = -9999999999999;
    1288           0 :     for (int i = 0; i < count; i++) {
    1289           0 :         if (state.dataPluginManager->trends[handle].values[i] > maximumValue) {
    1290           0 :             maximumValue = state.dataPluginManager->trends[handle].values[i];
    1291             :         }
    1292             :     }
    1293           0 :     return maximumValue;
    1294             : }
    1295             : #else
    1296             : Real64 PluginManager::getTrendVariableMax([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] int handle, [[maybe_unused]] int count)
    1297             : {
    1298             :     return 0.0;
    1299             : }
    1300             : #endif
    1301             : 
    1302             : #if LINK_WITH_PYTHON
    1303           0 : Real64 PluginManager::getTrendVariableSum(EnergyPlusData &state, int handle, int count)
    1304             : {
    1305           0 :     Real64 sum = 0.0;
    1306           0 :     for (int i = 0; i < count; i++) {
    1307           0 :         sum += state.dataPluginManager->trends[handle].values[i];
    1308             :     }
    1309           0 :     return sum;
    1310             : }
    1311             : #else
    1312             : Real64 PluginManager::getTrendVariableSum([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] int handle, [[maybe_unused]] int count)
    1313             : {
    1314             :     return 0.0;
    1315             : }
    1316             : #endif
    1317             : 
    1318             : #if LINK_WITH_PYTHON
    1319        3173 : Real64 PluginManager::getTrendVariableDirection(EnergyPlusData &state, int handle, int count)
    1320             : {
    1321        3173 :     auto &trend = state.dataPluginManager->trends[handle];
    1322        3173 :     Real64 timeSum = 0.0;
    1323        3173 :     Real64 valueSum = 0.0;
    1324        3173 :     Real64 crossSum = 0.0;
    1325        3173 :     Real64 powSum = 0.0;
    1326       15865 :     for (int i = 0; i < count; i++) {
    1327       12692 :         timeSum += trend.times[i];
    1328       12692 :         valueSum += trend.values[i];
    1329       12692 :         crossSum += trend.times[i] * trend.values[i];
    1330       12692 :         powSum += pow2(trend.times[i]);
    1331             :     }
    1332        3173 :     Real64 numerator = timeSum * valueSum - count * crossSum;
    1333        3173 :     Real64 denominator = pow_2(timeSum) - count * powSum;
    1334        3173 :     return numerator / denominator;
    1335             : }
    1336             : #else
    1337             : Real64 PluginManager::getTrendVariableDirection([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] int handle, [[maybe_unused]] int count)
    1338             : {
    1339             :     return 0.0;
    1340             : }
    1341             : #endif
    1342             : 
    1343             : #if LINK_WITH_PYTHON
    1344       11535 : size_t PluginManager::getTrendVariableHistorySize(EnergyPlusData &state, int handle)
    1345             : {
    1346       11535 :     return state.dataPluginManager->trends[handle].values.size();
    1347             : }
    1348             : #else
    1349             : size_t PluginManager::getTrendVariableHistorySize([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] int handle)
    1350             : {
    1351             :     return 0;
    1352             : }
    1353             : #endif
    1354             : 
    1355     2568312 : void PluginManager::updatePluginValues([[maybe_unused]] EnergyPlusData &state)
    1356             : {
    1357             : #if LINK_WITH_PYTHON
    1358     2580747 :     for (auto &trend : state.dataPluginManager->trends) {
    1359       12435 :         Real64 newVarValue = PluginManager::getGlobalVariableValue(state, trend.indexOfPluginVariable);
    1360       12435 :         trend.values.push_front(newVarValue);
    1361       12435 :         trend.values.pop_back();
    1362             :     }
    1363             : #endif
    1364     2568312 : }
    1365             : 
    1366             : #if LINK_WITH_PYTHON
    1367       68620 : Real64 PluginManager::getGlobalVariableValue(EnergyPlusData &state, int handle)
    1368             : {
    1369       68620 :     if (state.dataPluginManager->globalVariableValues.empty()) {
    1370           0 :         EnergyPlus::ShowFatalError(
    1371             :             state,
    1372             :             "Tried to access plugin global variable but it looks like there aren't any; use the PythonPlugin:Variables object to declare them.");
    1373             :     }
    1374             :     try {
    1375       68620 :         return state.dataPluginManager->globalVariableValues[handle]; // TODO: This won't be caught as an exception I think
    1376             :     } catch (...) {
    1377             :         EnergyPlus::ShowSevereError(state, format("Tried to access plugin global variable value at index {}", handle));
    1378             :         EnergyPlus::ShowContinueError(state,
    1379             :                                       format("Available handles range from 0 to {}", state.dataPluginManager->globalVariableValues.size() - 1));
    1380             :         EnergyPlus::ShowFatalError(state, "Plugin global variable problem causes program termination");
    1381             :     }
    1382             :     return 0.0;
    1383             : }
    1384             : #else
    1385             : Real64 PluginManager::getGlobalVariableValue([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] int handle)
    1386             : {
    1387             :     return 0.0;
    1388             : }
    1389             : #endif
    1390             : 
    1391             : #if LINK_WITH_PYTHON
    1392      498808 : void PluginManager::setGlobalVariableValue(EnergyPlusData &state, int handle, Real64 value)
    1393             : {
    1394      498808 :     if (state.dataPluginManager->globalVariableValues.empty()) {
    1395           0 :         EnergyPlus::ShowFatalError(state,
    1396             :                                    "Tried to set plugin global variable but it looks like there aren't any; use the PythonPlugin:GlobalVariables "
    1397             :                                    "object to declare them.");
    1398             :     }
    1399             :     try {
    1400      498808 :         state.dataPluginManager->globalVariableValues[handle] = value; // TODO: This won't be caught as an exception I think
    1401             :     } catch (...) {
    1402             :         EnergyPlus::ShowSevereError(state, format("Tried to set plugin global variable value at index {}", handle));
    1403             :         EnergyPlus::ShowContinueError(state,
    1404             :                                       format("Available handles range from 0 to {}", state.dataPluginManager->globalVariableValues.size() - 1));
    1405             :         EnergyPlus::ShowFatalError(state, "Plugin global variable problem causes program termination");
    1406             :     }
    1407      498808 : }
    1408             : #else
    1409             : void PluginManager::setGlobalVariableValue([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] int handle, [[maybe_unused]] Real64 value)
    1410             : {
    1411             : }
    1412             : #endif
    1413             : 
    1414             : #if LINK_WITH_PYTHON
    1415           6 : int PluginManager::getLocationOfUserDefinedPlugin(EnergyPlusData &state, std::string const &_programName)
    1416             : {
    1417          10 :     for (size_t handle = 0; handle < state.dataPluginManager->plugins.size(); handle++) {
    1418          14 :         auto const thisPlugin = state.dataPluginManager->plugins[handle];
    1419          10 :         if (EnergyPlus::UtilityRoutines::MakeUPPERCase(thisPlugin.emsAlias) == EnergyPlus::UtilityRoutines::MakeUPPERCase(_programName)) {
    1420           6 :             return handle;
    1421             :         }
    1422             :     }
    1423           0 :     return -1;
    1424             : }
    1425             : #else
    1426             : int PluginManager::getLocationOfUserDefinedPlugin([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] std::string const &_programName)
    1427             : {
    1428             :     return -1;
    1429             : }
    1430             : #endif
    1431             : 
    1432             : #if LINK_WITH_PYTHON
    1433      213588 : void PluginManager::runSingleUserDefinedPlugin(EnergyPlusData &state, int index)
    1434             : {
    1435      213588 :     state.dataPluginManager->plugins[index].run(state, EMSManager::EMSCallFrom::UserDefinedComponentModel);
    1436      213588 : }
    1437             : #else
    1438             : void PluginManager::runSingleUserDefinedPlugin([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] int index)
    1439             : {
    1440             : }
    1441             : #endif
    1442             : 
    1443             : #if LINK_WITH_PYTHON
    1444           0 : bool PluginManager::anyUnexpectedPluginObjects(EnergyPlusData &state)
    1445             : {
    1446           0 :     int numTotalThings = 0;
    1447           0 :     for (auto const &objToFind : state.dataPluginManager->objectsToFind) {
    1448           0 :         int instances = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, objToFind);
    1449           0 :         numTotalThings += instances;
    1450           0 :         if (numTotalThings == 1) {
    1451           0 :             ShowSevereMessage(state, "Found PythonPlugin objects in an IDF that is running in an API/Library workflow...this is invalid");
    1452             :         }
    1453           0 :         if (instances > 0) {
    1454           0 :             ShowContinueError(state, "Invalid PythonPlugin object type: " + objToFind);
    1455             :         }
    1456             :     }
    1457           0 :     return numTotalThings > 0;
    1458             : }
    1459             : #else
    1460             : bool PluginManager::anyUnexpectedPluginObjects([[maybe_unused]] EnergyPlusData &state)
    1461             : {
    1462             :     return false;
    1463             : }
    1464             : #endif
    1465             : 
    1466        2313 : } // namespace EnergyPlus::PluginManagement

Generated by: LCOV version 1.13