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
|