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
#4551 - Add minimum/maximum values to numeric OSArguments and use it in validateUserArgument #4621
Conversation
…instead of the deprecated Ruleset one
…in/max value now will show in measure.xml
… minimum finite value (2.2250738585072014e-308 not `- std::numeric_limits<T>::max()`
@@ -86,7 +86,7 @@ def initialize(logger=nil) | |||
@measures = {} # measure_dir => BCLMeasure | |||
@measure_info = {} # measure_dir => {osm_path => RubyUserScriptInfo} | |||
|
|||
eval(OpenStudio::Ruleset::infoExtractorRubyFunction) | |||
eval(OpenStudio::Measure::infoExtractorRubyFunction) |
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.
Modernize our script to not use the deprecated namespace
if argument.hasDomain | ||
min, max = argument.domainAsDouble | ||
# I'm a bit wary of rounding issues... I think 1e308 instead of | ||
# Float::MAX (1.7976931348623157e+308) is fine for our applications... | ||
if min > -1e308 # -Float::MAX | ||
arg[:min_value] = min | ||
end | ||
if max < 1e308 # Float::MAX | ||
arg[:max_value] = max | ||
end | ||
end |
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.
openstudio measure --compute-arguments
will now output the min/max.
@@ -763,7 +763,7 @@ namespace measure { | |||
return false; | |||
} | |||
|
|||
double minValue = std::numeric_limits<double>::min(); | |||
double minValue = std::numeric_limits<double>::lowest(); |
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.
Using min()
here means using the smallest positive value. NOT what was expected.
Use lowest()
which equals -max()
cf https://godbolt.org/z/K1aq87Yjh
std::numeric_limits<double>::max() = 1.7976931348623157e+308
std::numeric_limits<double>::min() = 2.2250738585072014e-308 # POSITIVE minimum finite value
std::numeric_limits<double>::lowest()= -1.7976931348623157e+308
if (argument.type() == OSArgumentType::Integer) { | ||
auto domain = argument.domainAsInteger(); | ||
auto& low = domain.front(); | ||
auto& high = domain.back(); | ||
if (low > std::numeric_limits<int>::lowest()) { | ||
minValue = std::to_string(low); | ||
} | ||
if (high < std::numeric_limits<int>::max()) { | ||
maxValue = std::to_string(high); | ||
} | ||
} else if (argument.type() == OSArgumentType::Double) { | ||
auto domain = argument.domainAsDouble(); | ||
auto& low = domain.front(); | ||
auto& high = domain.back(); | ||
if (low > std::numeric_limits<double>::lowest()) { | ||
minValue = std::to_string(low); | ||
} | ||
if (high < std::numeric_limits<double>::max()) { | ||
maxValue = std::to_string(high); | ||
} | ||
} |
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.
} else if (typeValue == OSArgumentType::Double) { | ||
auto value = user_argument.hasValue() ? user_argument.valueAsDouble() : user_argument.defaultValueAsDouble(); | ||
if (script_argument.hasDomain()) { | ||
// Validate it's in the domain | ||
auto domain = script_argument.domainAsDouble(); | ||
auto& low = domain.front(); | ||
auto& high = domain.back(); | ||
if (value < low || value > high) { | ||
registerError(fmt::format("{} User argument '{}' has a value '{}' that is not in the domain [{}, {}].", | ||
user_argument.type().valueName(), script_argument.name(), value, low, high)); | ||
result = false; | ||
} | ||
break; | ||
case OSArgumentType::String: | ||
case OSArgumentType::Choice: | ||
case OSArgumentType::Path: | ||
if (user_argument.hasValue()) { | ||
stepValues.push_back(WorkflowStepValue(user_argument.name(), user_argument.valueAsString())); | ||
} else if (user_argument.hasDefaultValue()) { | ||
stepValues.push_back(WorkflowStepValue(user_argument.name(), user_argument.defaultValueAsString())); | ||
} | ||
if (result) { | ||
stepValues.emplace_back(user_argument.name(), value); | ||
} |
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.
Do the actual validation of the Double/Integer domain inside valdiateUserArguments => registers an actual error if out of bounds
OSArgument arg = OSArgument::makeDoubleArgument("double_arg", true); | ||
arg.setMaxValue(10.0); | ||
result.push_back(arg); | ||
|
||
arg = OSArgument::makeIntegerArgument("int_arg", true); | ||
arg.setMinValue(0); | ||
result.push_back(arg); |
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.
New test
// call with a good value | ||
double_arg.setValue(-1.0); | ||
int_arg.setValue(1.0); | ||
EXPECT_TRUE(script.run(model, runner, user_arguments)); | ||
WorkflowStepResult result = runner.result(); | ||
ASSERT_TRUE(result.stepResult()); | ||
EXPECT_EQ(StepResult::Success, result.stepResult()->value()); | ||
EXPECT_EQ(0u, result.stepErrors().size()); | ||
EXPECT_EQ(0u, result.stepWarnings().size()); | ||
|
||
// Out of bound value for double_arg | ||
runner.reset(); | ||
double_arg.setValue(100.0); | ||
EXPECT_FALSE(script.run(model, runner, user_arguments)); | ||
result = runner.result(); | ||
ASSERT_TRUE(result.stepResult()); | ||
EXPECT_EQ(StepResult::Fail, result.stepResult()->value()); | ||
ASSERT_EQ(1u, result.stepErrors().size()); | ||
EXPECT_EQ( | ||
fmt::format("Double User argument 'double_arg' has a value '100' that is not in the domain [{}, 10].", std::numeric_limits<double>::lowest()), | ||
result.stepErrors()[0]); | ||
EXPECT_EQ(0u, result.stepWarnings().size()); | ||
|
||
// Out of bound value for int_arg | ||
runner.reset(); | ||
double_arg.setValue(1.0); | ||
int_arg.setValue(-3); | ||
EXPECT_FALSE(script.run(model, runner, user_arguments)); | ||
result = runner.result(); | ||
ASSERT_TRUE(result.stepResult()); | ||
EXPECT_EQ(StepResult::Fail, result.stepResult()->value()); | ||
ASSERT_EQ(1u, result.stepErrors().size()); | ||
EXPECT_EQ("Integer User argument 'int_arg' has a value '-3' that is not in the domain [0, 2147483647].", result.stepErrors()[0]); | ||
EXPECT_EQ(0u, result.stepWarnings().size()); |
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.
As can be seen, it does capture the issue of the out of bounds.
If you run the test with e2d00da (before I changed the std::numeric_limits<T>::min()
to std::numeric_limits<T>::lowest()
) it fails (I did that fix before extending the tests but reordered the commits to show the issue)
CI Results for 058d347:
|
@DavidGoldwasser Could you take a look at this one please? And confirm adding the min_value / max_value to the measure.xml is ok with you? One thing I also wanted to do was to replace OpenStudio/src/cli/openstudio_cli.rb Line 1483 in 0e7dda7
OpenStudio/src/cli/openstudio_cli.rb Line 1543 in 0e7dda7
Thoughts anyone? |
@@ -468,24 +470,42 @@ def get_arguments_from_measure_info(measure_info) | |||
elsif type == "Double".to_OSArgumentType | |||
arg[:units] = argument.units.get if argument.units.is_initialized | |||
arg[:default_value] = argument.defaultValueAsDouble if argument.hasDefaultValue | |||
|
|||
if argument.hasDomain |
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.
Does it still have domain if either min or max are set but not both?
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.
Domain is set as [std::numeric_limits<double>::lowest(), max]
if max only is provided, and if min only then [min, std::numeric_limits<double>::max()]
|
||
if argument.hasDomain | ||
min, max = argument.domainAsInteger | ||
if min != -2147483648 |
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.
I'm confused what the purpose of this is? Same for above with 1e308.
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.
Same answer as above: when only min or max is provided, one of the domain boundaries is going to be std::numeric_limits<T>::lowest()
or std::numeric_limits<T>::max()
.
- In the case of an integer that's +- 2147483648
- for a double that's +-1.7976931348623157e+308
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.
So if you did indeed set a min less than lowest()
or a max greater than max()
, wouldn't it not set/write those values?
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.
I don't see how you could do that though. It'd be an overflow error, but mostly you just can't do it, bindings included.
cf:
$ irb
2.7.2 :002 > 1e308
=> 1.0e+308
2.7.2 :003 > 1e309
=> Infinity
result.push_back(arg); | ||
|
||
arg = OSArgument::makeIntegerArgument("int_arg", true); | ||
arg.setMinValue(0); |
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.
Test with a negative?
Pull request overview
Pull Request Author
src/model/test
)src/energyplus/Test
)src/osversion/VersionTranslator.cpp
)Labels:
IDDChange
APIChange
Pull Request - Ready for CI
so that CI builds your PRReview Checklist
This will not be exhaustively relevant to every PR.