-
Notifications
You must be signed in to change notification settings - Fork 190
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
base: develop
Are you sure you want to change the base?
Changes from all commits
fed3286
b06a217
b6c9ed1
dd11368
4ee1bd1
ec32948
29f59fe
f0004a6
cd8c5c5
6fdcba3
d8d31e0
512c251
2fbde16
5036e4a
e291e53
a423d78
8ee112d
f5d94e2
7e2e3fb
0ea61e8
fbbca00
16430e7
7b00836
a8975c3
2d380ce
fc08324
2e67d5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
""" | ||
|
||
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") | ||
alfalfa.exposeGlobalVariable("global_1", "global variable") | ||
super().run(model, runner, user_arguments) # Do **NOT** remove this line | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find the placement of this call to super() quite strange. Is there a reason it's done after exposing the alfalfa things? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is not a reason, I'll fix that in the next commit. |
||
|
||
return True | ||
|
||
AlfalfaMeasure().registerWithApplication() |
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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
||
|
@@ -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().getPoints().empty()); | ||
|
||
openstudio::measure::OSArgumentMap arguments; | ||
measurePtr->run(model, runner, arguments); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there anything you can test here?! Make sure your alfalfa.exposeXXX calls actually did something There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check the runner's alfalfa has points at least. |
||
EXPECT_EQ(5, runner.alfalfa().getPoints().size()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ | |
from . import openstudioosversion as osversion | ||
from . import openstudioradiance as radiance | ||
from . import openstudiosdd as sdd | ||
from . import openstudioutilitiesalfalfa as alfalfa | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't follow the other utilities convention. Maybe I'll discover a good reason deeper in the review. |
||
from .openstudioutilities import * | ||
from .openstudioutilitiesbcl import * | ||
from .openstudioutilitiescore import * | ||
|
@@ -52,6 +53,7 @@ | |
import openstudioisomodel as isomodel | ||
import openstudiomeasure as measure | ||
import openstudiomodel as model | ||
import openstudioutilitiesalfalfa as alfalfa | ||
|
||
# These are already included in the `model` namespace via Model.i | ||
# import openstudiomodelcore as modelcore | ||
|
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok this one is first in line and has the comment |
||
|
||
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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this method work with a ModelObject ? |
||
|
||
# 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> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.