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 native support for measure argument methods in OsLib_HelperMethods, try 2 #5135

Merged
merged 50 commits into from
Apr 12, 2024

Conversation

joseph-robertson
Copy link
Collaborator

@joseph-robertson joseph-robertson commented Apr 2, 2024

Pull request overview

Add methods that return Json::Value:

  • runner.getArgumentValues(script_arguments, user_arguments)
  • runner.getPastStepValuesForMeasure(measure_name)
  • runner.getPastStepValuesForName(step_name)

Pull Request Author

  • Model API Changes / Additions
  • Any new or modified fields have been implemented in the EnergyPlus ForwardTranslator (and ReverseTranslator as appropriate)
  • Model API methods are tested (in src/model/test)
  • EnergyPlus ForwardTranslator Tests (in src/energyplus/Test)
  • If a new object or method, added a test in NREL/OpenStudio-resources: Add Link
  • If needed, added VersionTranslation rules for the objects (src/osversion/VersionTranslator.cpp)
  • Verified that C# bindings built fine on Windows, partial classes used as needed, etc.
  • All new and existing tests passes
  • If methods have been deprecated, update rest of code to use the new methods

Labels:

  • If change to an IDD file, add the label IDDChange
  • If breaking existing API, add the label APIChange
  • If deemed ready, add label Pull Request - Ready for CI so that CI builds your PR

Review Checklist

This will not be exhaustively relevant to every PR.

  • Perform a Code Review on GitHub
  • Code Style, strip trailing whitespace, etc.
  • All related changes have been implemented: model changes, model tests, FT changes, FT tests, VersionTranslation, OS App
  • Labeling is ok
  • If defect, verify by running develop branch and reproducing defect, then running PR and reproducing fix
  • If feature, test running new feature, try creative ways to break it
  • CI status: all green or justified

@joseph-robertson joseph-robertson added the Pull Request - Ready for CI This pull request if finalized and is ready for continuous integration verification prior to merge. label Apr 2, 2024
@joseph-robertson joseph-robertson self-assigned this Apr 2, 2024
Comment on lines +1064 to +1066
if (hasValue()) {
root["value"] = valueAsBool();
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm very puzzled why I didn't do hasValue() orginally... I can't recall if this is a mistake or if I had a reason.

ANyways, we should ladder the ifs

if (hasValue()) {
  ...
} else if (hasDefaultValue()) {
   ...
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

OH. Nevermind. I didn't see we're writing to two different keys, value and default_value

Copy link
Collaborator

Choose a reason for hiding this comment

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

Right ok, I was just mimic'ing this

def get_arguments_from_measure(measure_dir, measure)
result = []
begin
# this type was not wrapped with SWIG until OS 1.11.2
measure.arguments.each do |argument|
type = argument.type
arg = {}
arg[:name] = argument.name
arg[:display_name] = argument.displayName
arg[:description] = argument.description.to_s
arg[:type] = argument.type
arg[:required] = argument.required
arg[:model_dependent] = argument.modelDependent
case type
when 'Boolean'
if argument.defaultValue.is_initialized
default_value = argument.defaultValue.get
arg[:default_value] = (default_value.downcase == "true")
end
when 'Double'
arg[:units] = argument.units.get if argument.units.is_initialized
arg[:default_value] = argument.defaultValue.get.to_f if argument.defaultValue.is_initialized
arg[:min_value] = argument.minValue.get.to_f if argument.minValue.is_initialized
arg[:max_value] = argument.maxValue.get.to_f if argument.maxValue.is_initialized
when 'Integer'
arg[:units] = argument.units.get if argument.units.is_initialized
arg[:default_value] = argument.defaultValue.get.to_i if argument.defaultValue.is_initialized
arg[:min_value] = argument.minValue.get.to_i if argument.minValue.is_initialized
arg[:max_value] = argument.maxValue.get.to_i if argument.maxValue.is_initialized
when 'String'
arg[:default_value] = argument.defaultValue.get if argument.defaultValue.is_initialized
when 'Choice'
arg[:default_value] = argument.defaultValue.get if argument.defaultValue.is_initialized
arg[:choice_values] = []
argument.choiceValues.each {|value| arg[:choice_values] << value}
arg[:choice_display_names] = []
argument.choiceDisplayNames.each {|value| arg[:choice_display_names] << value}
when 'Path'
arg[:default_value] = argument.defaultValue.get if argument.defaultValue.is_initialized
end
result << arg
end
rescue
info = get_measure_info(measure_dir, measure, "", OpenStudio::Model::OptionalModel.new, OpenStudio::OptionalWorkspace.new)
return get_arguments_from_measure_info(info)
end
return force_encoding(result, 'utf-8')
end

Copy link
Collaborator

Choose a reason for hiding this comment

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

Alright, this seems just fine to me.

When using a BCLMeasure to inspect arguments, the JSON is unchanged because the OSArgument does not have a value.

EXPECT_EQ(1.0, d);

double d2 = runner.getDoubleArgumentValue(requiredDoubleArgument2.name(), argumentMap);
EXPECT_EQ(234892384234.39485923845834534, d2);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
EXPECT_EQ(234892384234.39485923845834534, d2);
EXPECT_DOUBLE_EQ(234892384234.39485923845834534, d2);

optionalChoiceArgument2.setValue(openstudio::toString(boiler2.handle()));
argumentVector.push_back(optionalChoiceArgument2);

std::map<std::string, OSArgument> argumentMap = convertOSArgumentVectorToMap(argumentVector);
Copy link
Collaborator

Choose a reason for hiding this comment

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

You should emplace an extra arg in there for testing purposes

@@ -784,6 +786,68 @@ namespace measure {
return result;
}

Json::Value OSRunner::getArgumentValues(std::vector<OSArgument>& script_arguments, const std::map<std::string, OSArgument>& user_arguments) {
Json::Value argument_values;
std::string name;
Copy link
Collaborator

Choose a reason for hiding this comment

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

scoping. declare it as const std::string name inside the loop, it's safer

@@ -17,6 +17,8 @@
#include "../utilities/core/Assert.hpp"
#include "../utilities/core/PathHelpers.hpp"

#include <boost/lexical_cast.hpp>
Copy link
Collaborator

Choose a reason for hiding this comment

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

?

@@ -784,6 +786,68 @@ namespace measure {
return result;
}

Json::Value OSRunner::getArgumentValues(std::vector<OSArgument>& script_arguments, const std::map<std::string, OSArgument>& user_arguments) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

So, I'm following https://github.com/NREL/openstudio-extension-gem/blob/bb25c593c6fd3eab214ec92d7d1d5e7625937e2e/lib/openstudio/extension/core/os_lib_helper_methods.rb#L30-L67

But I think it should be different. What I think it should do is, to also handle optionals:

for each measure_arg in script_arguments:
   if user_argument includes it, and it has a value:
       use it
   else if the **measure_arg** has default value:
       use that, because the user_arguments could theoretically have had default_value modifications
   else: empty

Comment on lines +1157 to +1159
if (hasValue()) {
root["value"] = openstudio::toString(valueAsPath());
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

The more I think about it, the more convinced I become that we should just have some convenience functions in OSArgument

    Json::Value valueAsJSON() const;
    Json::Value defaultValueAsJSON() const;
    Json::Value domainAsJSON() const;

They should be exposed to the bindings, and would convert nicely to Ruby/Python type. Maybe we even go crazy and call them just value() / defaultValue


Also, I think the crazyness we go through when passing arguments to the run method has to go. We should be able to just pass a ruby hash to it, instead of sprinkling every damn measure test with some nastinesss like

argument_map = OpenStudio::Measure.convertOSArgumentVectorToMap(arguments)
# create hash of argument values
args_hash = {}
args_hash['space_name'] = ''
# populate argument with specified hash value if specified
arguments.each do |arg|
temp_arg_var = arg.clone
if args_hash.key?(arg.name)
assert(temp_arg_var.setValue(args_hash[arg.name]))
end
argument_map[arg.name] = temp_arg_var
end

return argument_values;
}

Json::Value OSRunner::getPastStepValuesForMeasure(const std::string& measure_name) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

  • I'm still confused about the difference between the getPastStepValuesForMeasure and getPastStepValuesForName, but I'll figure that out.
  • The above is mostyl due to how unreadable both methods are. The flow logic places an unnecessarily high cognitive burden on the reader. I'll revamp
  • Both of these should be marked const.
    • Generally speaking, the entire existing OSRunner class is completely messed up as far as const-correctness and staticness of methods that can be (like getOptionalXXXArgument), but I'm wary of changing this since it's an ABI break

@@ -784,6 +786,68 @@ namespace measure {
return result;
}

Json::Value OSRunner::getArgumentValues(std::vector<OSArgument>& script_arguments, const std::map<std::string, OSArgument>& user_arguments) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be static actually.

@joseph-robertson joseph-robertson marked this pull request as draft April 11, 2024 16:15
…S >= 10.14 supports it, and E+ is 10.15 minimum anyways
* Try to align getPastStepValuesForName on check_upstream_measure_for_arg in what it picks for measure name
* getPastStepValuesForMeasure: allow matching on either the WorkflowStep's measureDirName or name, or the WorkflowStepResult's measureName
* Scope the test blocks for readability and avoiding side effects (less chances of making a mistake)
…true

* Move logic to Variant::isTruish to determine if a variant is trueish (whether that's bool true, str = "true", int != 0, double != 0.0
* Add  a OSRunner::prepareForMeasureRun() that takes no arg, protected, and friend OSWorkflow so we can use it without the penatly of instantiating an OSMeasure
* In Apply Measure, when __SKIP__, correctly call prepareForMEasureRun before incrementing the step. And do load the BCLMeasure to write the resultInfo like the workflow-gem was doing
…ap<string, int> as swig template globally

I kinda like the usability

```
OpenStudio::StepResult.getLookupMap
=> std::map<std::string,int,std::less< std::string >,std::allocator< std::pair< std::string const,int > > > {"FAIL"=>1,"NA"=>-1,"NOTAPPLICABLE"=>-1,"PASS"=>0,"SKIP"=>-2,"SKIPPED"=>-2,"SUCCESS"=>0}

OpenStudio::StepResult.getLookupMap.to_h
=> {"FAIL"=>1, "NA"=>-1, "NOTAPPLICABLE"=>-1, "PASS"=>0, "SKIP"=>-2, "SKIPPED"=>-2, "SUCCESS"=>0}
```


python

```
In [16]: openstudio.StepResult.getLookupMap().asdict()
Out[16]: 
{'FAIL': 1,
 'NA': -1,
 'NOTAPPLICABLE': -1,
 'PASS': 0,
 'SKIP': -2,
 'SKIPPED': -2,
 'SUCCESS': 0}
```
```
object = OpenStudio::Measure::OSArgument.makeChoiceArgument('object', {'handle1' => 'choice1', 'handle2' => 'choice2'}, true, true)

object.choiceValues
=> ["handle1", "handle2"]
object.choiceValueDisplayNames
=> ["choice1", "choice2"]

```
@joseph-robertson joseph-robertson marked this pull request as ready for review April 12, 2024 20:35
@joseph-robertson joseph-robertson merged commit d2f0bdd into develop Apr 12, 2024
1 of 6 checks passed
@joseph-robertson joseph-robertson deleted the lib-helper-methods2 branch April 12, 2024 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Pull Request - Ready for CI This pull request if finalized and is ready for continuous integration verification prior to merge.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants