Skip to content

Commit ce0ac7f

Browse files
authored
Merge pull request #5367 from NREL/4830-ReportingMeasure-modelOutputRequests
#4830 - Add a new ReportingMeasure::modelOutputRequests(model, runner, argument_map) that runs before E+ FT
2 parents 98207d2 + 678ee71 commit ce0ac7f

39 files changed

Lines changed: 1707 additions & 43 deletions

File tree

python/engine/PythonEngine.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,39 @@ int PythonEngine::numberOfArguments(ScriptObject& methodObject, std::string_view
413413
return numberOfArguments;
414414
}
415415

416+
bool PythonEngine::hasMethod(ScriptObject& methodObject, std::string_view methodName, bool overriden_only) {
417+
auto val = std::any_cast<PythonObject>(methodObject.object);
418+
if (PyObject_HasAttrString(val.obj_, methodName.data()) == 0) {
419+
return false;
420+
}
421+
PyObject* method = PyObject_GetAttrString(val.obj_, methodName.data()); // New reference
422+
if (PyMethod_Check(method) == 0) {
423+
// Should never happen with modelOutputRequests since the Base class (C++) has it
424+
return false;
425+
}
426+
Py_DECREF(method);
427+
if (!overriden_only) {
428+
return true;
429+
}
430+
431+
// equivalent to getattr(instance_obj.__class__, method_name) == getattr(instance_obj.__class__.__bases__[0], method_name)
432+
PyTypeObject* class_type = Py_TYPE(val.obj_); // PyObject_Type returns a strong (New) reference, not needed for us
433+
// cppcheck-suppress cstyleCast
434+
PyObject* class_method = PyObject_GetAttrString((PyObject*)class_type, methodName.data()); // New reference
435+
436+
assert(class_type->tp_base != nullptr);
437+
// cppcheck-suppress cstyleCast
438+
auto* base = (PyTypeObject*)class_type->tp_base;
439+
// cppcheck-suppress cstyleCast
440+
PyObject* base_method = PyObject_GetAttrString((PyObject*)base, methodName.data()); // New reference
441+
442+
bool result = class_method != base_method;
443+
Py_DECREF(class_method);
444+
Py_DECREF(base_method);
445+
446+
return result;
447+
}
448+
416449
} // namespace openstudio
417450

418451
extern "C"

python/engine/PythonEngine.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class PythonEngine final : public ScriptEngine
3838

3939
virtual int numberOfArguments(ScriptObject& methodObject, std::string_view methodName) override;
4040

41+
virtual bool hasMethod(ScriptObject& methodObject, std::string_view methodName, bool overriden_only) override;
42+
4143
protected:
4244
void* getAs_impl(ScriptObject& obj, const std::type_info&) override;
4345

python/engine/test/PythonEngine_GTest.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,22 @@ TEST_F(PythonEngineFixture, AlfalfaMeasure) {
174174
measurePtr->run(model, runner, arguments);
175175
EXPECT_EQ(5, runner.alfalfa().points().size());
176176
}
177+
178+
TEST_F(PythonEngineFixture, hasMethod) {
179+
{
180+
const std::string classAndDirName = "ReportingMeasureWithoutModelOutputs";
181+
const auto scriptPath = getScriptPath(classAndDirName);
182+
auto measureScriptObject = (*thisEngine)->loadMeasure(scriptPath, classAndDirName);
183+
EXPECT_FALSE((*thisEngine)->hasMethod(measureScriptObject, "doesNotExists"));
184+
EXPECT_TRUE((*thisEngine)->hasMethod(measureScriptObject, "modelOutputRequests", false)); // overriden_only = false
185+
EXPECT_FALSE((*thisEngine)->hasMethod(measureScriptObject, "modelOutputRequests"));
186+
}
187+
{
188+
const std::string classAndDirName = "ReportingMeasureWithModelOutputs";
189+
const auto scriptPath = getScriptPath(classAndDirName);
190+
auto measureScriptObject = (*thisEngine)->loadMeasure(scriptPath, classAndDirName);
191+
EXPECT_FALSE((*thisEngine)->hasMethod(measureScriptObject, "doesNotExists"));
192+
EXPECT_TRUE((*thisEngine)->hasMethod(measureScriptObject, "modelOutputRequests", false)); // overriden_only = false
193+
EXPECT_TRUE((*thisEngine)->hasMethod(measureScriptObject, "modelOutputRequests"));
194+
}
195+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""insert your copyright here.
2+
3+
# see the URL below for information on how to write OpenStudio measures
4+
# http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/
5+
"""
6+
7+
from pathlib import Path
8+
9+
import openstudio
10+
11+
12+
class ReportingMeasureWithModelOutputs(openstudio.measure.ReportingMeasure):
13+
"""An ReportingMeasure."""
14+
15+
def name(self):
16+
return "ReportingMeasureWithModelOutputs"
17+
18+
def description(self):
19+
return "DESCRIPTION_TEXT"
20+
21+
def modeler_description(self):
22+
return "MODELER_DESCRIPTION_TEXT"
23+
24+
def arguments(self, model: openstudio.model.Model):
25+
args = openstudio.measure.OSArgumentVector()
26+
return args
27+
28+
def outputs(self):
29+
outs = openstudio.measure.OSOutputVector()
30+
return outs
31+
32+
def modelOutputRequests(
33+
self,
34+
model: openstudio.model.Model,
35+
runner: openstudio.measure.OSRunner,
36+
user_arguments: openstudio.measure.OSArgumentMap,
37+
) -> bool:
38+
return True
39+
40+
def energyPlusOutputRequests(
41+
self, runner: openstudio.measure.OSRunner, user_arguments: openstudio.measure.OSArgumentMap
42+
):
43+
result = openstudio.IdfObjectVector()
44+
return result
45+
46+
def run(
47+
self,
48+
runner: openstudio.measure.OSRunner,
49+
user_arguments: openstudio.measure.OSArgumentMap,
50+
):
51+
super().run(runner, user_arguments)
52+
return True
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0"?>
2+
<measure>
3+
<schema_version>3.1</schema_version>
4+
<name>reporting_measure_with_model_outputs</name>
5+
<uid>9971eb7b-3225-4795-9690-9941a7471ce0</uid>
6+
<version_id>be88754a-3d7f-433e-9912-94a2993c9463</version_id>
7+
<version_modified>2025-03-14T13:15:30Z</version_modified>
8+
<xml_checksum>1DA817AF</xml_checksum>
9+
<class_name>ReportingMeasureWithModelOutputs</class_name>
10+
<display_name>ReportingMeasureWithModelOutputs</display_name>
11+
<description>DESCRIPTION_TEXT</description>
12+
<modeler_description>MODELER_DESCRIPTION_TEXT</modeler_description>
13+
<arguments />
14+
<outputs />
15+
<provenances />
16+
<tags>
17+
<tag>Reporting.QAQC</tag>
18+
</tags>
19+
<attributes>
20+
<attribute>
21+
<name>Measure Type</name>
22+
<value>ReportingMeasure</value>
23+
<datatype>string</datatype>
24+
</attribute>
25+
<attribute>
26+
<name>Measure Language</name>
27+
<value>Python</value>
28+
<datatype>string</datatype>
29+
</attribute>
30+
</attributes>
31+
<files>
32+
<file>
33+
<version>
34+
<software_program>OpenStudio</software_program>
35+
<identifier>3.9.0</identifier>
36+
<min_compatible>3.9.0</min_compatible>
37+
</version>
38+
<filename>measure.py</filename>
39+
<filetype>py</filetype>
40+
<usage_type>script</usage_type>
41+
<checksum>CFB17771</checksum>
42+
</file>
43+
</files>
44+
</measure>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""insert your copyright here.
2+
3+
# see the URL below for information on how to write OpenStudio measures
4+
# http://nrel.github.io/OpenStudio-user-documentation/reference/measure_writing_guide/
5+
"""
6+
7+
from pathlib import Path
8+
9+
import openstudio
10+
11+
12+
class ReportingMeasureWithoutModelOutputs(openstudio.measure.ReportingMeasure):
13+
"""An ReportingMeasure."""
14+
15+
def name(self):
16+
return "ReportingMeasureWithoutModelOutputs"
17+
18+
def description(self):
19+
return "DESCRIPTION_TEXT"
20+
21+
def modeler_description(self):
22+
return "MODELER_DESCRIPTION_TEXT"
23+
24+
def arguments(self, model: openstudio.model.Model):
25+
args = openstudio.measure.OSArgumentVector()
26+
return args
27+
28+
def outputs(self):
29+
outs = openstudio.measure.OSOutputVector()
30+
return outs
31+
32+
def energyPlusOutputRequests(
33+
self, runner: openstudio.measure.OSRunner, user_arguments: openstudio.measure.OSArgumentMap
34+
):
35+
result = openstudio.IdfObjectVector()
36+
return result
37+
38+
def run(
39+
self,
40+
runner: openstudio.measure.OSRunner,
41+
user_arguments: openstudio.measure.OSArgumentMap,
42+
):
43+
super().run(runner, user_arguments)
44+
return True
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0"?>
2+
<measure>
3+
<schema_version>3.1</schema_version>
4+
<name>reporting_measure_without_model_outputs</name>
5+
<uid>a0876945-a15b-469d-bc22-90f133720e7c</uid>
6+
<version_id>49740c2c-1804-4a4e-ace5-c30b6d350c5f</version_id>
7+
<version_modified>2025-03-14T13:15:17Z</version_modified>
8+
<xml_checksum>1DA817AF</xml_checksum>
9+
<class_name>ReportingMeasureWithoutModelOutputs</class_name>
10+
<display_name>ReportingMeasureWithoutModelOutputs</display_name>
11+
<description>DESCRIPTION_TEXT</description>
12+
<modeler_description>MODELER_DESCRIPTION_TEXT</modeler_description>
13+
<arguments />
14+
<outputs />
15+
<provenances />
16+
<tags>
17+
<tag>Reporting.QAQC</tag>
18+
</tags>
19+
<attributes>
20+
<attribute>
21+
<name>Measure Type</name>
22+
<value>ReportingMeasure</value>
23+
<datatype>string</datatype>
24+
</attribute>
25+
<attribute>
26+
<name>Measure Language</name>
27+
<value>Python</value>
28+
<datatype>string</datatype>
29+
</attribute>
30+
</attributes>
31+
<files>
32+
<file>
33+
<version>
34+
<software_program>OpenStudio</software_program>
35+
<identifier>3.9.0</identifier>
36+
<min_compatible>3.9.0</min_compatible>
37+
</version>
38+
<filename>measure.py</filename>
39+
<filetype>py</filetype>
40+
<usage_type>script</usage_type>
41+
<checksum>816BF07D</checksum>
42+
</file>
43+
</files>
44+
</measure>

0 commit comments

Comments
 (0)