Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Utility for Generating Alfalfa Metadata to OpenStudio #5236

Merged
merged 43 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9843b0f
Basic structure for Alfalfa OpenStudio integration
TShapinsky Aug 16, 2024
3cd4267
do formatting
TShapinsky Aug 16, 2024
41dbcc9
Add ability to create alfalfa points directly from openstudio objects'
TShapinsky Aug 19, 2024
e077c72
consolidate functions and allow for idf and modelobjects to be passed…
TShapinsky Aug 21, 2024
eba24de
move to impl pattern for desired functionality and introduce testing …
TShapinsky Aug 28, 2024
8d231c3
fix formatting
TShapinsky Aug 29, 2024
e55e00e
consolidate logic and expand testing
TShapinsky Aug 30, 2024
186d31c
formatting
TShapinsky Aug 30, 2024
1cef96b
documentation and testing
TShapinsky Sep 4, 2024
4b5b5a9
formatting
TShapinsky Sep 4, 2024
a41f6d8
export correctly to dll
TShapinsky Sep 4, 2024
3dbc86b
add json saving to postprocess workflow step
TShapinsky Sep 6, 2024
134e8a5
fix test building for ubuntu and be more thoughtful about assert vs e…
TShapinsky Sep 9, 2024
7925401
Add example measures for workflow test
TShapinsky Sep 9, 2024
299fa00
include file writing into alfalfaJSON test suite
TShapinsky Sep 9, 2024
30c49e7
fix target name
TShapinsky Sep 9, 2024
5e12354
add check for contents of generated json
TShapinsky Sep 10, 2024
f6b9368
change to exposeFromObject and exposeFromComponent. Remove redundant …
TShapinsky Sep 10, 2024
d3aed8c
add tests for cases where objects do not have all requsite properties
TShapinsky Sep 10, 2024
10147d1
improve testing and error messages
TShapinsky Sep 11, 2024
280f99c
fix ctest paths
TShapinsky Sep 12, 2024
31820bb
fix ruby bindings
TShapinsky Sep 13, 2024
41abf4a
Raw string litteral and absolute minimal testing for PythonEngine alf…
jmarrec Sep 20, 2024
9ed8e01
Fixup AlfalfaJSON code
jmarrec Sep 20, 2024
578381e
Lint AlfalfaPoint
jmarrec Sep 20, 2024
29f5b9a
Lint up the rest
jmarrec Sep 20, 2024
a3f527d
move alfalfa to top level and reimagine Component implementation
TShapinsky Oct 10, 2024
28a9c80
getPoints() to points()
TShapinsky Oct 10, 2024
c5c0cb1
formatting
TShapinsky Oct 10, 2024
f2c2956
reset utilities cmakelists now that we are not using it
TShapinsky Oct 10, 2024
1677007
commit alfalfajson_gtest
TShapinsky Oct 10, 2024
ff2ff6f
move checks for object type into components
TShapinsky Oct 10, 2024
66b50e3
formatting
TShapinsky Oct 10, 2024
3014739
fix dll export
TShapinsky Oct 11, 2024
be526c3
undo overzealous clang-format
TShapinsky Oct 11, 2024
a221335
undo overzealous clang-format
TShapinsky Oct 11, 2024
3cd641b
undo overzealous clang-format
TShapinsky Oct 11, 2024
662ddbb
use simplified API header
TShapinsky Oct 11, 2024
1de9181
omit clone() from swig
TShapinsky Oct 11, 2024
5fe1207
cleanup imports and clang-tidy
TShapinsky Oct 15, 2024
d1f6cff
try using runtime wrapping for make fmt happy on ubuntu 22.04
TShapinsky Oct 16, 2024
5015251
fix alfalfa ctests
TShapinsky Oct 17, 2024
f54b39d
fix linking of alfalfa ruby bindings
TShapinsky Oct 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions CMake/RunCommands.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#
# RunCommands.cmake
#
# Run a set of commands in series.
# This is useful for creating tests which consist of more than one command.
#
# Commands are passed as -DCMD<n> as n is between 1 and 9
#
# Example Usage:
# add_test(test_name COMMAND ${CMAKE_COMMAND}
# "-DCMD1=echo hello"
# "-DCMD2=echo goodbye"
# -P ${CMAKE_SOURCE_DIR}/CMake/RunCommands)
#
# An error will be throw if any command returns a non-zero exit code or if a command cannot be found.
# When an error is thrown no further commands will be executed.
#
# STDOUT and STDERR are untouched and reach output as normal.
# Before running each command is echoed to STDOUT.

foreach(CMD_IDX RANGE 1 10)
if(DEFINED CMD${CMD_IDX})
separate_arguments(ARGS NATIVE_COMMAND PROGRAM SEPARATE_ARGS "${CMD${CMD_IDX}}")
if(ARGS)
list(POP_FRONT ARGS CMD)
execute_process(COMMAND ${CMD} ${ARGS} COMMAND_ECHO STDOUT COMMAND_ERROR_IS_FATAL ANY)
else()
message(FATAL_ERROR "Cannot find command to run: '${CMD${CMD_IDX}}'")
endif()
else()
break()
endif()
endforeach()
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,7 @@ set(project_directories
airflow
isomodel
osversion
alfalfa
measure
sdd
lib
Expand Down
39 changes: 39 additions & 0 deletions python/engine/test/AlfalfaMeasure/measure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import typing

import openstudio


class AlfalfaMeasure(openstudio.measure.ModelMeasure):
def name(self):
return "Alfalfa Measure"

def modeler_description(self):
return "The method attempts to build an alfalfa json in the measure"


def arguments(self, model: typing.Optional[openstudio.model.Model] = None):
args = openstudio.measure.OSArgumentVector()

return args

def run(
self,
model: openstudio.model.Model,
runner: openstudio.measure.OSRunner,
user_arguments: openstudio.measure.OSArgumentMap,
):
"""
define what happens when the measure is run
"""
super().run(model, runner, user_arguments) # Do **NOT** remove this line

alfalfa = runner.alfalfa()
alfalfa.exposeConstant(10, "safe value")
alfalfa.exposeMeter("Facility:Electricity", "Facility Electricity")
alfalfa.exposeActuator("somehting", "another thing", "key", "example actuator")
alfalfa.exposeOutputVariable("Whole Building", "Facility Total Purchased Electricity Energy", "output variable")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a change? I seem to recall that these originally took ModelObject instances. I think when we @TShapinsky last spoke you were contemplating a change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A risk I can think of is that the final names might not be known at ModelMeasure time.

alfalfa.exposeGlobalVariable("global_1", "global variable")

return True

AlfalfaMeasure().registerWithApplication()
59 changes: 59 additions & 0 deletions python/engine/test/AlfalfaMeasure/measure.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0"?>
<measure>
<schema_version>3.1</schema_version>
<name>alfalfa_measure</name>
<uid>812d3ebf-c89b-4b93-b400-110ca060b2bb</uid>
<version_id>25ad8ea8-b28b-4f45-93a6-76f056c28ca8</version_id>
<version_modified>2023-11-10T10:47:04Z</version_modified>
<xml_checksum>33A29C78</xml_checksum>
<class_name>AlfalfaMeasure</class_name>
<display_name>Alfalfa Measure</display_name>
<description></description>
<modeler_description>The method attempts to build an alfalfa json in the measure</modeler_description>
<arguments />
<outputs />
<provenances />
<tags>
<tag>Envelope.Opaque</tag>
</tags>
<attributes>
<attribute>
<name>Measure Function</name>
<value>Measure</value>
<datatype>string</datatype>
</attribute>
<attribute>
<name>Requires EnergyPlus Results</name>
<value>false</value>
<datatype>boolean</datatype>
</attribute>
<attribute>
<name>Measure Type</name>
<value>ModelMeasure</value>
<datatype>string</datatype>
</attribute>
<attribute>
<name>Measure Language</name>
<value>Python</value>
<datatype>string</datatype>
</attribute>
<attribute>
<name>Uses SketchUp API</name>
<value>false</value>
<datatype>boolean</datatype>
</attribute>
</attributes>
<files>
<file>
<version>
<software_program>OpenStudio</software_program>
<identifier>3.4.1</identifier>
<min_compatible>3.4.1</min_compatible>
</version>
<filename>measure.py</filename>
<filetype>py</filetype>
<usage_type>script</usage_type>
<checksum>E787E0E0</checksum>
</file>
</files>
</measure>
36 changes: 36 additions & 0 deletions python/engine/test/PythonEngine_GTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "measure/ModelMeasure.hpp"
#include "measure/OSArgument.hpp"
#include "measure/OSMeasure.hpp"
#include "measure/OSRunner.hpp"
#include "model/Model.hpp"
#include "scriptengine/ScriptEngine.hpp"

Expand Down Expand Up @@ -134,3 +135,38 @@ RecursionError: maximum recursion depth exceeded)",
EXPECT_EQ(expected_exception, error);
}
}

TEST_F(PythonEngineFixture, AlfalfaMeasure) {
const std::string classAndDirName = "AlfalfaMeasure";

const auto scriptPath = getScriptPath(classAndDirName);
auto measureScriptObject = (*thisEngine)->loadMeasure(scriptPath, classAndDirName);
auto* measurePtr = (*thisEngine)->getAs<openstudio::measure::ModelMeasure*>(measureScriptObject);

ASSERT_EQ(measurePtr->name(), "Alfalfa Measure");

std::string workflow_json = R"json(
{
"seed": "../seed.osm",
"weather_file": "../weather.epw",
"steps": [
{
"arguments": {},
"description": "The method attempts to build an alfalfa json in the measure",
"measure_dir_name": "AlfalfaMeasure",
"modeler_description": "The method attempts to build an alfalfa json in the measure",
"name": "AlfalfaMeasure"
}
]
}
)json";

openstudio::model::Model model;
openstudio::WorkflowJSON workflow = *openstudio::WorkflowJSON::load(workflow_json);
openstudio::measure::OSRunner runner(workflow);
EXPECT_TRUE(runner.alfalfa().points().empty());

openstudio::measure::OSArgumentMap arguments;
measurePtr->run(model, runner, arguments);
TShapinsky marked this conversation as resolved.
Show resolved Hide resolved
EXPECT_EQ(5, runner.alfalfa().points().size());
}
2 changes: 2 additions & 0 deletions python/module/openstudio.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from . import openstudioosversion as osversion
from . import openstudioradiance as radiance
from . import openstudiosdd as sdd
from . import openstudioualfalfa as alfalfa
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's that extra "u" in there? I think you meant openstudioalfalfa @TShapinsky

from .openstudioutilities import *
from .openstudioutilitiesbcl import *
from .openstudioutilitiescore import *
Expand All @@ -52,6 +53,7 @@
import openstudioisomodel as isomodel
import openstudiomeasure as measure
import openstudiomodel as model
import openstudioalfalfa as alfalfa

# These are already included in the `model` namespace via Model.i
# import openstudiomodelcore as modelcore
Expand Down
22 changes: 22 additions & 0 deletions resources/Examples/compact_osw/compact_alfalfa.osw
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"weather_file": "srrl_2013_amy.epw",
"seed_file": "seb.osm",
"steps": [
{
"measure_dir_name": "AlfalfaModelPython",
"arguments": {}
},
{
"measure_dir_name": "AlfalfaModelRuby",
"arguments": {}
},
{
"measure_dir_name": "AlfalfaEPlusPython",
"arguments": {}
},
{
"measure_dir_name": "AlfalfaEPlusRuby",
"arguments": {}
},
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""insert your copyright here.

# see the URL below for information on how to write OpenStudio measures
# http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/
"""

import openstudio


class AlfalfaEPlusPython(openstudio.measure.EnergyPlusMeasure):
"""An EnergyPlusMeasure."""

def name(self):
"""Returns the human readable name.

Measure name should be the title case of the class name.
The measure name is the first contact a user has with the measure;
it is also shared throughout the measure workflow, visible in the OpenStudio Application,
PAT, Server Management Consoles, and in output reports.
As such, measure names should clearly describe the measure's function,
while remaining general in nature
"""
return "AlfalfaEPlusPython"

def description(self):
"""Human readable description.

The measure description is intended for a general audience and should not assume
that the reader is familiar with the design and construction practices suggested by the measure.
"""
return "DESCRIPTION_TEXT"

def modeler_description(self):
"""Human readable description of modeling approach.

The modeler description is intended for the energy modeler using the measure.
It should explain the measure's intent, and include any requirements about
how the baseline model must be set up, major assumptions made by the measure,
and relevant citations or references to applicable modeling resources
"""
return "MODELER_DESCRIPTION_TEXT"

def arguments(self, workspace: openstudio.Workspace):
"""Prepares user arguments for the measure.

Measure arguments define which -- if any -- input parameters the user may set before running the measure.
"""
args = openstudio.measure.OSArgumentVector()

return args

def run(
self,
workspace: openstudio.Workspace,
runner: openstudio.measure.OSRunner,
user_arguments: openstudio.measure.OSArgumentMap,
):
"""Defines what happens when the measure is run."""
super().run(workspace, runner, user_arguments) # Do **NOT** remove this line
TShapinsky marked this conversation as resolved.
Show resolved Hide resolved

if not (runner.validateUserArguments(self.arguments(workspace), user_arguments)):
return False

alfalfa = runner.alfalfa()

# Test Meters
alfalfa.exposeMeter("Electricity:Facility", "Electricity Meter String:EPlus:Python")

meter_object = openstudio.IdfObject.load("Output:Meter, Electricity:Facility;").get()
alfalfa.exposeFromObject(meter_object, "Electricity Meter IDF:Eplus:Python")
TShapinsky marked this conversation as resolved.
Show resolved Hide resolved

# Test Output Variables
alfalfa.exposeOutputVariable("EMS", "my_var", "Output Variable String:EPlus:Python")

ems_output_variable_object = openstudio.IdfObject.load("EnergyManagementSystem:OutputVariable,My Var,my_var,,ZoneTimestep,,;").get()
alfalfa.exposeFromObject(ems_output_variable_object, "EMS Output Variable IDF:EPlus:Python")

output_variable_object = openstudio.IdfObject.load("Output:Variable,EMS,my_var,timstep;").get()
alfalfa.exposeFromObject(output_variable_object, "Output Variable IDF:EPlus:Python")

# Test Global Variables
alfalfa.exposeGlobalVariable("my_var", "Global Variable String:EPlus:Python")

global_variable_object = openstudio.IdfObject.load("EnergyManagementSystem:GlobalVariable,my_var;").get()
alfalfa.exposeFromObject(global_variable_object, "Global Variable IDF:EPlus:Python")

# Test Actuators
alfalfa.exposeActuator("component_name", "componen_type", "control_type", "Actuator String:EPlus:Python")

actuator_object = openstudio.IdfObject.load("EnergyManagementSystem:Actuator,MyActuator,component_name,component_type,control_type;").get()
alfalfa.exposeFromObject(actuator_object, "Actuator IDF:EPlus:Python")

return True


# register the measure to be used by the application
AlfalfaEPlusPython().registerWithApplication()
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?xml version="1.0"?>
<measure>
<schema_version>3.1</schema_version>
<name>alfalfa_e_plus_python</name>
<uid>d3160f18-a73e-4470-a574-03bdf39c9ef2</uid>
<version_id>e8e471ba-b61a-47b7-ac7b-162f337d38c2</version_id>
<version_modified>2024-09-09T17:02:46Z</version_modified>
<xml_checksum>00000000</xml_checksum>
<class_name>AlfalfaEPlusPython</class_name>
<display_name>AlfalfaEPlusPython</display_name>
<description>DESCRIPTION_TEXT</description>
<modeler_description>MODELER_DESCRIPTION_TEXT</modeler_description>
<arguments>
<argument>
<name>zone_name</name>
<display_name>New zone name</display_name>
<description>This name will be used as the name of the new zone.</description>
<type>String</type>
<required>true</required>
<model_dependent>false</model_dependent>
</argument>
</arguments>
<outputs />
<provenances />
<tags>
<tag>Envelope.Fenestration</tag>
</tags>
<attributes>
<attribute>
<name>Measure Type</name>
<value>EnergyPlusMeasure</value>
<datatype>string</datatype>
</attribute>
<attribute>
<name>Measure Language</name>
<value>Python</value>
<datatype>string</datatype>
</attribute>
</attributes>
<files>
<file>
<filename>LICENSE.md</filename>
<filetype>md</filetype>
<usage_type>license</usage_type>
<checksum>CD7F5672</checksum>
</file>
<file>
<filename>.gitkeep</filename>
<filetype>gitkeep</filetype>
<usage_type>doc</usage_type>
<checksum>00000000</checksum>
</file>
<file>
<version>
<software_program>OpenStudio</software_program>
<identifier>3.8.0</identifier>
</version>
<filename>measure.py</filename>
<filetype>py</filetype>
<usage_type>script</usage_type>
<checksum>88CBEF7D</checksum>
</file>
<file>
<filename>test_alfalfa_e_plus_python.py</filename>
<filetype>py</filetype>
<usage_type>test</usage_type>
<checksum>E636CDC6</checksum>
</file>
</files>
</measure>
Loading
Loading