Skip to content

Commit

Permalink
Merge pull request #5312 from NREL/python-plugin-search-paths
Browse files Browse the repository at this point in the history
Wrap PythonPlugin:SearchPaths
  • Loading branch information
joseph-robertson authored Dec 19, 2024
2 parents 2d4cbea + 187352f commit 73e12b9
Show file tree
Hide file tree
Showing 20 changed files with 954 additions and 2 deletions.
47 changes: 47 additions & 0 deletions resources/model/OpenStudio.idd
Original file line number Diff line number Diff line change
Expand Up @@ -38660,6 +38660,53 @@ OS:EnergyManagementSystem:ConstructionIndexVariable,

\group Python Plugin System

OS:PythonPlugin:SearchPaths,
\memo Add directories to the search path for Python plugin modules
\memo The directory containing the EnergyPlus executable file is
\memo automatically added so that the Python interpreter can find the
\memo packaged up pyenergyplus Python package.
\memo By default, the current working directory and input file directory
\memo are also added to the search path. However, this object allows
\memo modifying this behavior. With this object, searching these directories
\memo can be disabled, and users can add supplemental search paths
\memo that point to libraries of plugin scripts.
\unique-object
\min-fields 3
\extensible:1
A1, \field Handle
\type handle
\required-field
A2, \field Add Current Working Directory to Search Path
\note Adding the current working directory allows Python to find
\note plugin scripts in the current directory.
\note required-field disabled as it has a default
\type choice
\key Yes
\key No
\required-field
A3, \field Add Input File Directory to Search Path
\note Enabling this will allow Python to find plugin scripts in the
\note same directory as the running input file, even if that is not
\note the current working directory.
\note required-field disabled as it has a default
\type choice
\key Yes
\key No
\required-field
A4, \field Add epin Environment Variable to Search Path
\note The "epin" environment variable is set by some EnergyPlus interfaces
\note in order to let EnergyPlus find external files in special locations.
\note If this is enabled, and that variable is set, the value of the variable
\note will be added to the Python search path.
\type choice
\key Yes
\key No
\required-field
A5; \field Search Path 1
\type alpha
\begin-extensible
\retaincase

OS:PythonPlugin:Instance,
\memo A single plugin to be executed during the simulation, which can contain multiple calling points
\memo for the same class instance by overriding multiple calling point methods.
Expand Down
2 changes: 2 additions & 0 deletions src/energyplus/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ set(${target_name}_src
ForwardTranslator/ForwardTranslatePythonPluginVariable.cpp
ForwardTranslator/ForwardTranslatePythonPluginTrendVariable.cpp
ForwardTranslator/ForwardTranslatePythonPluginOutputVariable.cpp
ForwardTranslator/ForwardTranslatePythonPluginSearchPaths.cpp
ForwardTranslator/ForwardTranslateRefractionExtinctionGlazing.cpp
ForwardTranslator/ForwardTranslateRefrigerationAirChiller.cpp
ForwardTranslator/ForwardTranslateRefrigerationCase.cpp
Expand Down Expand Up @@ -791,6 +792,7 @@ set(${target_name}_test_src
Test/PythonPluginVariable_GTest.cpp
Test/PythonPluginOutputVariable_GTest.cpp
Test/PythonPluginTrendVariable_GTest.cpp
Test/PythonPluginSearchPaths_GTest.cpp
Test/RunPeriod_GTest.cpp
Test/RunPeriodControlDaylightSavingTime_GTest.cpp
Test/RunPeriodControlSpecialDays_GTest.cpp
Expand Down
7 changes: 7 additions & 0 deletions src/energyplus/ForwardTranslator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2433,6 +2433,11 @@ namespace energyplus {
retVal = translatePythonPluginOutputVariable(obj);
break;
}
case openstudio::IddObjectType::OS_PythonPlugin_SearchPaths: {
auto obj = modelObject.cast<PythonPluginSearchPaths>();
retVal = translatePythonPluginSearchPaths(obj);
break;
}
case openstudio::IddObjectType::OS_RadianceParameters: {
// no-op
break;
Expand Down Expand Up @@ -3602,6 +3607,8 @@ namespace energyplus {
IddObjectType::OS_ExternalInterface_FunctionalMockupUnitImport_To_Schedule,
IddObjectType::OS_ExternalInterface_FunctionalMockupUnitImport_To_Variable,

IddObjectType::
OS_PythonPlugin_SearchPaths, // this FT intentionally happens before PythonPlugin_Instance so that we can't end up with two PythonPlugin_SearchPaths objects
IddObjectType::OS_PythonPlugin_Instance,
IddObjectType::OS_PythonPlugin_Variable,
IddObjectType::OS_PythonPlugin_TrendVariable,
Expand Down
3 changes: 3 additions & 0 deletions src/energyplus/ForwardTranslator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ namespace model {
class PythonPluginVariable;
class PythonPluginTrendVariable;
class PythonPluginOutputVariable;
class PythonPluginSearchPaths;
class RefractionExtinctionGlazing;
class RefrigerationAirChiller;
class RefrigerationCase;
Expand Down Expand Up @@ -1256,6 +1257,8 @@ namespace energyplus {

boost::optional<IdfObject> translatePythonPluginOutputVariable(model::PythonPluginOutputVariable& modelObject);

boost::optional<IdfObject> translatePythonPluginSearchPaths(model::PythonPluginSearchPaths& modelObject);

boost::optional<IdfObject> translateRefractionExtinctionGlazing(model::RefractionExtinctionGlazing& modelObject);

boost::optional<IdfObject> translateRefrigerationAirChiller(model::RefrigerationAirChiller& modelObject);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/***********************************************************************************************************************
* OpenStudio(R), Copyright (c) 2008-2023, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer.
*
* (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
*
* (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
* derived from this software without specific prior written permission from the respective party.
*
* (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
* may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
* written permission from Alliance for Sustainable Energy, LLC.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
* STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
***********************************************************************************************************************/

#include "../ForwardTranslator.hpp"
#include "../../model/Model.hpp"

#include "../../model/PythonPluginSearchPaths.hpp"

#include "../../utilities/idf/IdfExtensibleGroup.hpp"

#include <utilities/idd/PythonPlugin_SearchPaths_FieldEnums.hxx>
#include <utilities/idd/IddEnums.hxx>

constexpr static auto pythonSearchPathsName = "Python Plugin Search Paths";

using namespace openstudio::model;

namespace openstudio {

namespace energyplus {

boost::optional<IdfObject> ForwardTranslator::translatePythonPluginSearchPaths(model::PythonPluginSearchPaths& modelObject) {

auto searchPaths = modelObject.searchPaths();
if (searchPaths.empty()) {
return boost::none;
}

IdfObject idfObject = createAndRegisterIdfObject(openstudio::IddObjectType::PythonPlugin_SearchPaths, modelObject);

idfObject.setName(pythonSearchPathsName);

// Add Current Working Directory to Search Path: Optional Boolean
if (modelObject.addCurrentWorkingDirectorytoSearchPath()) {
idfObject.setString(PythonPlugin_SearchPathsFields::AddCurrentWorkingDirectorytoSearchPath, "Yes");
} else {
idfObject.setString(PythonPlugin_SearchPathsFields::AddCurrentWorkingDirectorytoSearchPath, "No");
}

// Add Input File Directory to Search Path: Optional Boolean
if (modelObject.addInputFileDirectorytoSearchPath()) {
idfObject.setString(PythonPlugin_SearchPathsFields::AddInputFileDirectorytoSearchPath, "Yes");
} else {
idfObject.setString(PythonPlugin_SearchPathsFields::AddInputFileDirectorytoSearchPath, "No");
}

// Add epin Environment Variable to Search Path: Optional Boolean
if (modelObject.addepinEnvironmentVariabletoSearchPath()) {
idfObject.setString(PythonPlugin_SearchPathsFields::AddepinEnvironmentVariabletoSearchPath, "Yes");
} else {
idfObject.setString(PythonPlugin_SearchPathsFields::AddepinEnvironmentVariabletoSearchPath, "No");
}

// Search Path
for (const openstudio::path& searchPath : modelObject.searchPaths()) {
IdfExtensibleGroup eg = idfObject.pushExtensibleGroup();
eg.setString(PythonPlugin_SearchPathsExtensibleFields::SearchPath, searchPath.generic_string());
}

return idfObject;
} // End of translate function

} // end namespace energyplus
} // end namespace openstudio
167 changes: 167 additions & 0 deletions src/energyplus/Test/PythonPluginSearchPaths_GTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/***********************************************************************************************************************
* OpenStudio(R), Copyright (c) 2008-2023, Alliance for Sustainable Energy, LLC, and other contributors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer.
*
* (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
*
* (3) Neither the name of the copyright holder nor the names of any contributors may be used to endorse or promote products
* derived from this software without specific prior written permission from the respective party.
*
* (4) Other than as required in clauses (1) and (2), distributions in any form of modifications or other derivative works
* may not use the "OpenStudio" trademark, "OS", "os", or any other confusingly similar designation without specific prior
* written permission from Alliance for Sustainable Energy, LLC.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE UNITED STATES GOVERNMENT, OR THE UNITED
* STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
***********************************************************************************************************************/

#include <gtest/gtest.h>
#include "EnergyPlusFixture.hpp"

#include "../ForwardTranslator.hpp"

#include "../../model/Model.hpp"
#include "../../model/PythonPluginSearchPaths.hpp"
#include "../../model/PythonPluginSearchPaths_Impl.hpp"
#include "../../model/ExternalFile.hpp"
#include "../../model/ExternalFile_Impl.hpp"
#include "../../model/PythonPluginInstance.hpp"
#include "../../model/PythonPluginInstance_Impl.hpp"

#include "../../utilities/idf/Workspace.hpp"
#include "../../utilities/idf/IdfObject.hpp"
#include "../../utilities/idf/WorkspaceObject.hpp"
#include "../../utilities/idf/IdfExtensibleGroup.hpp"
#include "../../utilities/idf/WorkspaceExtensibleGroup.hpp"
#include "../../utilities/core/PathHelpers.hpp"
// E+ FieldEnums
#include <utilities/idd/IddEnums.hxx>
#include <utilities/idd/IddFactory.hxx>
#include <utilities/idd/PythonPlugin_SearchPaths_FieldEnums.hxx>

#include <resources.hxx>

using namespace openstudio::energyplus;
using namespace openstudio::model;
using namespace openstudio;

TEST_F(EnergyPlusFixture, ForwardTranslator_PythonPluginSearchPaths) {

ForwardTranslator ft;

Model m;
PythonPluginSearchPaths pythonPluginSearchPaths = m.getUniqueModelObject<PythonPluginSearchPaths>();

pythonPluginSearchPaths.setName("My PythonPluginSearchPaths");

EXPECT_TRUE(pythonPluginSearchPaths.searchPaths().empty());
// No search paths; not translated
{
EXPECT_TRUE(pythonPluginSearchPaths.setAddCurrentWorkingDirectorytoSearchPath(false)); // Opposite from IDD default
EXPECT_TRUE(pythonPluginSearchPaths.setAddInputFileDirectorytoSearchPath(false)); // Opposite from IDD default
EXPECT_TRUE(pythonPluginSearchPaths.setAddepinEnvironmentVariabletoSearchPath(false)); // Opposite from IDD default

const Workspace w = ft.translateModel(m);

const auto idfObjs = w.getObjectsByType(IddObjectType::PythonPlugin_SearchPaths);
ASSERT_EQ(0u, idfObjs.size());
}

{
EXPECT_TRUE(pythonPluginSearchPaths.setAddCurrentWorkingDirectorytoSearchPath(true));
EXPECT_TRUE(pythonPluginSearchPaths.setAddInputFileDirectorytoSearchPath(true));
EXPECT_TRUE(pythonPluginSearchPaths.setAddepinEnvironmentVariabletoSearchPath(true));

std::vector<std::string> searchPaths({"/path/to/lib1", "/path/to/lib2"});
EXPECT_TRUE(pythonPluginSearchPaths.setSearchPaths(searchPaths));
EXPECT_EQ(2u, pythonPluginSearchPaths.searchPaths().size());

const Workspace w = ft.translateModel(m);

const auto idfObjs = w.getObjectsByType(IddObjectType::PythonPlugin_SearchPaths);
ASSERT_EQ(1u, idfObjs.size());

const auto& idfObject = idfObjs.front();
EXPECT_EQ("Yes", idfObject.getString(PythonPlugin_SearchPathsFields::AddCurrentWorkingDirectorytoSearchPath).get());
EXPECT_EQ("Yes", idfObject.getString(PythonPlugin_SearchPathsFields::AddInputFileDirectorytoSearchPath).get());
EXPECT_EQ("Yes", idfObject.getString(PythonPlugin_SearchPathsFields::AddepinEnvironmentVariabletoSearchPath).get());

ASSERT_EQ(2u, idfObject.extensibleGroups().size());
for (int i = 0; i < 2; ++i) {
EXPECT_EQ(searchPaths[i], idfObject.extensibleGroups()[i].getString(PythonPlugin_SearchPathsExtensibleFields::SearchPath).get());
}
}

{
EXPECT_TRUE(pythonPluginSearchPaths.setAddCurrentWorkingDirectorytoSearchPath(false)); // Opposite from IDD default
EXPECT_TRUE(pythonPluginSearchPaths.setAddInputFileDirectorytoSearchPath(false)); // Opposite from IDD default
EXPECT_TRUE(pythonPluginSearchPaths.setAddepinEnvironmentVariabletoSearchPath(false)); // Opposite from IDD default

std::vector<std::string> searchPaths({"/path/to/lib1", "/path/to/lib2"});
EXPECT_TRUE(pythonPluginSearchPaths.setSearchPaths(searchPaths));
EXPECT_EQ(2u, pythonPluginSearchPaths.searchPaths().size());

const Workspace w = ft.translateModel(m);

const auto idfObjs = w.getObjectsByType(IddObjectType::PythonPlugin_SearchPaths);
ASSERT_EQ(1u, idfObjs.size());

const auto& idfObject = idfObjs.front();
EXPECT_EQ("No", idfObject.getString(PythonPlugin_SearchPathsFields::AddCurrentWorkingDirectorytoSearchPath).get());
EXPECT_EQ("No", idfObject.getString(PythonPlugin_SearchPathsFields::AddInputFileDirectorytoSearchPath).get());
EXPECT_EQ("No", idfObject.getString(PythonPlugin_SearchPathsFields::AddepinEnvironmentVariabletoSearchPath).get());

ASSERT_EQ(2u, idfObject.extensibleGroups().size());
for (int i = 0; i < 2; ++i) {
EXPECT_EQ(searchPaths[i], idfObject.extensibleGroups()[i].getString(PythonPlugin_SearchPathsExtensibleFields::SearchPath).get());
}
}

{
EXPECT_TRUE(pythonPluginSearchPaths.setAddCurrentWorkingDirectorytoSearchPath(false)); // Opposite from IDD default
EXPECT_TRUE(pythonPluginSearchPaths.setAddInputFileDirectorytoSearchPath(false)); // Opposite from IDD default
EXPECT_TRUE(pythonPluginSearchPaths.setAddepinEnvironmentVariabletoSearchPath(false)); // Opposite from IDD default

openstudio::path p = resourcesPath() / toPath("model/PythonPluginThermochromicWindow.py");
ASSERT_TRUE(exists(p));

boost::optional<ExternalFile> externalfile = ExternalFile::getExternalFile(m, openstudio::toString(p));
ASSERT_TRUE(externalfile) << "Path doesn't exist: '" << p << "'";

PythonPluginInstance pythonPluginInstance(*externalfile, "ZN_1_wall_south_Window_1_Control");

std::vector<std::string> searchPaths({"/path/to/lib1", "/path/to/lib2"});
EXPECT_TRUE(pythonPluginSearchPaths.setSearchPaths(searchPaths));
EXPECT_EQ(2u, pythonPluginSearchPaths.searchPaths().size());

const Workspace w = ft.translateModel(m);

const auto idfObjs = w.getObjectsByType(IddObjectType::PythonPlugin_SearchPaths);
ASSERT_EQ(1u, idfObjs.size());

const auto& idfObject = idfObjs.front();
EXPECT_EQ("No", idfObject.getString(PythonPlugin_SearchPathsFields::AddCurrentWorkingDirectorytoSearchPath).get());
EXPECT_EQ("No", idfObject.getString(PythonPlugin_SearchPathsFields::AddInputFileDirectorytoSearchPath).get());
EXPECT_EQ("No", idfObject.getString(PythonPlugin_SearchPathsFields::AddepinEnvironmentVariabletoSearchPath).get());

ASSERT_EQ(3u, idfObject.extensibleGroups().size());
for (int i = 0; i < 2; ++i) {
EXPECT_EQ(searchPaths[i], idfObject.extensibleGroups()[i].getString(PythonPlugin_SearchPathsExtensibleFields::SearchPath).get());
}
// since PythonPlugin_Instance is translated after PythonPlugin_SearchPaths, this search path is appended to the existing search paths
EXPECT_EQ(openstudio::toString(externalfile->filePath().parent_path()),
idfObject.extensibleGroups()[2].getString(PythonPlugin_SearchPathsExtensibleFields::SearchPath).get());
}
}
4 changes: 4 additions & 0 deletions src/model/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,9 @@ set(${target_name}_src
PythonPluginOutputVariable.hpp
PythonPluginOutputVariable_Impl.hpp
PythonPluginOutputVariable.cpp
PythonPluginSearchPaths.hpp
PythonPluginSearchPaths_Impl.hpp
PythonPluginSearchPaths.cpp
RadianceParameters.hpp
RadianceParameters_Impl.hpp
RadianceParameters.cpp
Expand Down Expand Up @@ -2221,6 +2224,7 @@ set(${target_name}_test_src
test/PythonPluginVariable_GTest.cpp
test/PythonPluginTrendVariable_GTest.cpp
test/PythonPluginOutputVariable_GTest.cpp
test/PythonPluginSearchPaths_GTest.cpp
test/RadianceParameters_GTest.cpp
test/RefractionExtinctionGlazing_GTest.cpp
test/RefrigerationAirChiller_GTest.cpp
Expand Down
Loading

0 comments on commit 73e12b9

Please sign in to comment.