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

Open
wants to merge 27 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fed3286
Basic structure for Alfalfa OpenStudio integration
TShapinsky Aug 16, 2024
b06a217
do formatting
TShapinsky Aug 16, 2024
b6c9ed1
Add ability to create alfalfa points directly from openstudio objects'
TShapinsky Aug 19, 2024
dd11368
consolidate functions and allow for idf and modelobjects to be passed…
TShapinsky Aug 21, 2024
4ee1bd1
move to impl pattern for desired functionality and introduce testing …
TShapinsky Aug 28, 2024
ec32948
fix formatting
TShapinsky Aug 29, 2024
29f59fe
consolidate logic and expand testing
TShapinsky Aug 30, 2024
f0004a6
formatting
TShapinsky Aug 30, 2024
cd8c5c5
documentation and testing
TShapinsky Sep 4, 2024
6fdcba3
formatting
TShapinsky Sep 4, 2024
d8d31e0
export correctly to dll
TShapinsky Sep 4, 2024
512c251
add json saving to postprocess workflow step
TShapinsky Sep 6, 2024
2fbde16
fix test building for ubuntu and be more thoughtful about assert vs e…
TShapinsky Sep 9, 2024
5036e4a
Add example measures for workflow test
TShapinsky Sep 9, 2024
e291e53
include file writing into alfalfaJSON test suite
TShapinsky Sep 9, 2024
a423d78
fix target name
TShapinsky Sep 9, 2024
8ee112d
add check for contents of generated json
TShapinsky Sep 10, 2024
f5d94e2
change to exposeFromObject and exposeFromComponent. Remove redundant …
TShapinsky Sep 10, 2024
7e2e3fb
add tests for cases where objects do not have all requsite properties
TShapinsky Sep 10, 2024
0ea61e8
improve testing and error messages
TShapinsky Sep 11, 2024
fbbca00
fix ctest paths
TShapinsky Sep 12, 2024
16430e7
fix ruby bindings
TShapinsky Sep 13, 2024
7b00836
Raw string litteral and absolute minimal testing for PythonEngine alf…
jmarrec Sep 20, 2024
a8975c3
Fixup AlfalfaJSON code
jmarrec Sep 20, 2024
2d380ce
Lint AlfalfaPoint
jmarrec Sep 20, 2024
fc08324
Lint up the rest
jmarrec Sep 20, 2024
2e67d5c
Merge pull request #5259 from NREL/alfalfa_utility2
TShapinsky Sep 27, 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
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
"""

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")
super().run(model, runner, user_arguments) # Do **NOT** remove this line
Copy link
Collaborator

@jmarrec jmarrec Sep 20, 2024

Choose a reason for hiding this comment

The 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?
If not, I'd keep it first in the routine, so people are less likely to remove it when they copy your measure.

Copy link
Member

Choose a reason for hiding this comment

The 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()
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().getPoints().empty());

openstudio::measure::OSArgumentMap arguments;
measurePtr->run(model, runner, arguments);
Copy link
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Collaborator

Choose a reason for hiding this comment

The 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());
}
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 openstudioutilitiesalfalfa 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.

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 *
Expand All @@ -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
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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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")
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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>
Loading
Loading