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

epJSON helper functions to get field values #8738

Merged
merged 34 commits into from Jun 12, 2021
Merged

epJSON helper functions to get field values #8738

merged 34 commits into from Jun 12, 2021

Conversation

mjwitte
Copy link
Contributor

@mjwitte mjwitte commented Apr 30, 2021

Pull request overview

  • Addresses Add helper functions to validate and get an epJSON object and field value #7632
  • Added three new input processor functions to simplify input processing using epJSON field_names instead of getObjectItem.
    getObjectSchemaProps(state, CurrentModuleObject)
    getAlphaFieldValue(state, CurrentModuleObject, objectFields, objectSchemaProps, "alpha_field_name")
    getRealFieldValue(state, CurrentModuleObject, objectFields, objectSchemaProps, "real_field_name")
    getIntFieldValue(state, CurrentModuleObject, objectFields, objectSchemaProps, "integer_field_name")

ToDos/Questions:

  • Add support for extensible fields - do we even have a pattern for that somewhere - grabbing extensible fields without using getObjectItem?
  • Look for more code re-use opportunities in InputProcessor (e.g. getDefaultValue also searches for the object and field schema)
  • Undo the Airflownetwork getinput changes to avoid conflicts with other AFN PRs.
  • Demonstrate the new functions in a getInput function somewhere
  • Add extensible fields to the unit tests

Pull Request Author

Add to this list or remove from it as applicable. This is a simple templated set of guidelines.

  • Title of PR should be user-synopsis style (clearly understandable in a standalone changelog context)
  • Label the PR with at least one of: Defect, Refactoring, NewFeature, Performance, and/or DoNoPublish
  • Pull requests that impact EnergyPlus code must also include unit tests to cover enhancement or defect repair
  • Author should provide a "walkthrough" of relevant code changes using a GitHub code review comment process
  • If any diffs are expected, author must demonstrate they are justified using plots and descriptions
  • If changes fix a defect, the fix should be demonstrated in plots and descriptions
  • If any defect files are updated to a more recent version, upload new versions here or on DevSupport
  • If IDD requires transition, transition source, rules, ExpandObjects, and IDFs must be updated, and add IDDChange label
  • If structural output changes, add to output rules file and add OutputChange label
  • If adding/removing any LaTeX docs or figures, update that document's CMakeLists file dependencies

Reviewer

This will not be exhaustively relevant to every PR.

  • Perform a Code Review on GitHub
  • If branch is behind develop, merge develop and build locally to check for side effects of the merge
  • 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
  • Check that performance is not impacted (CI Linux results include performance check)
  • Run Unit Test(s) locally
  • Check any new function arguments for performance impacts
  • Verify IDF naming conventions and styles, memos and notes and defaults
  • If new idf included, locally check the err file and other outputs

@mjwitte mjwitte added the DoNotPublish Includes changes that shouldn't be reported in the changelog label Apr 30, 2021
@rraustad
Copy link
Contributor

Your on a roll. Is getFieldName on your list? I'd love that for eio reporting.

@rraustad
Copy link
Contributor

OK, so I guess that would just be a reformatting of the fieldname passed to these functions.

@mjwitte
Copy link
Contributor Author

mjwitte commented Apr 30, 2021

Is getFieldName on your list? I'd love that for eio reporting.

Do you mean pass in the epJSON field name "thermal_conductivity" and return the IDD field name "Thermal Conductivity"?

@rraustad
Copy link
Contributor

Yes, I had an issue with getting field names during the sizer function refactor.

@rraustad
Copy link
Contributor

And what happens when we change thermal_conductivity to material_thermal_conductivity? Are the field names frozen from here forward? I better get #8691 wrapped up quick.

@rraustad
Copy link
Contributor

Seems it needs to be an index/vector, but then that index can't change in the future.

Comment on lines 403 to 404
Real64 coeff = state.dataInputProcessing->inputProcessor->getRealFieldValue(
state, CurrentModuleObject, fields, objectSchemaProps, "air_mass_flow_coefficient_at_reference_conditions");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@amirroth When trying to apply the new getFieldValue functions in an actual getInput function, it seemed silly to have patterns like

Real64 coeff = 0.0;
state.dataInputProcessing->inputProcessor->getFieldValue(state, CurrentModuleObject, fields, objectSchemaProps, "air_mass_flow_coefficient_at_reference_conditions", coeff);

By moving to explicitly typed functions, this reduces to one line. Seems more readable.

Real64 coeff = state.dataInputProcessing->inputProcessor->getRealFieldValue(state, CurrentModuleObject, fields, objectSchemaProps, "air_mass_flow_coefficient_at_reference_conditions");

if (is_empty) {
isDefaulted = findDefault(value, schema_field_obj);
} else {
value = -99999; // autosize and autocalculate
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess you assume here if !is_empty then the input is either blank or is set to Autosize.

I explicitly tested for that but maybe with this method you don't have to test. Anything other than the word autosize would be caught by the inputprocessor.

                auto tempFieldVal = fields.at("heating_supply_air_flow_rate");
                if (tempFieldVal == "Autosize") {
                    loc_m_HeatingSAFMethod_SAFlow = DataSizing::AutoSize;
                } else {
                    loc_m_HeatingSAFMethod_SAFlow = fields.at("heating_supply_air_flow_rate");
                }

Do you need to check for?

   \autosizable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not 100% certain about that. In getRealFieldvalue I mimicked the code in findDefault for handling \autosize. It seems a bit fragile to assume if there's a string in a numeric field then it's autosize (or autocalculate). I'm not sure if illegal use of autosize or nonesense words get trapped during initial input processing (and that could be different if the incoming file is idf vs epJSON). Need to do some testing on that.

@mjwitte mjwitte added Refactoring Includes code changes that don't change the functionality of the program, just perform refactoring and removed DoNotPublish Includes changes that shouldn't be reported in the changelog labels May 5, 2021
numericFieldValue =
state->dataInputProcessing->inputProcessor->getRealFieldValue(*state, obj_type1, wh1, objectSchemaProps, "heater_maximum_capacity");
EXPECT_EQ(numericFieldValue, -99999);
// Even a field that is not autoszable will return -99999 here (assuming that gets checked upon epJSON input processing)
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess it is safe to assume this will always be caught. I wonder if this is actually unit tested somewhere.

** Severe  ** <root>[Building][Building][loads_convergence_tolerance_value] - Value type "string" for input "autosize" not permitted by 'type' constraint.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure why it says this input field type is "constraint"?

"loads_convergence_tolerance_value": {
    "type": "number",

@mjwitte mjwitte marked this pull request as ready for review May 25, 2021 18:35
@mjwitte mjwitte requested a review from Myoldmopar May 25, 2021 18:36
@mjwitte
Copy link
Contributor Author

mjwitte commented May 25, 2021

This is ready for review. There is a design doc that shows prototype getInput blocks using these new functions. It still seems like a lot of lines of code are required to set things up before processing the individual fields. Suggestions are welecome.

}

Real64 InputProcessor::getRealFieldValue(
EnergyPlusData &state, std::string const &objectWord, json const &ep_object, json const &schema_obj_props, std::string const &fieldName)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does it make sense to have separate functions for Real and Int?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question. I started to do that and then realized that the epJSON schema does not differentiate between real and integer: "type": "number"

Copy link
Contributor

Choose a reason for hiding this comment

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

getRealFieldVal could be changed to getNumericFieldVal if there is no differentiation between int and real. I wonder if returning a real will have any effect when reading into an int variable but since we do that now anyway with getObjectItem's Numbers(x) it shouldn't be a problem.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you wanted to return int or Real, you would need to have separate functions since they'd likely have the same input parameter signature and only change the return type.

Copy link
Contributor

Choose a reason for hiding this comment

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

I will also say with the following, which I saw in an outdated comment in this PR:

Real64 coeff = 0.0;
state.dataInputProcessing->inputProcessor->getFieldValue(state, CurrentModuleObject, fields, objectSchemaProps, "air_mass_flow_coefficient_at_reference_conditions", coeff);

It does seem odd to not directly return the fieldValue, however, this function could be overloaded to include int or string as input types with their own unique functions. Such as below:

int coeff = 0.0;
state.dataInputProcessing->inputProcessor->getFieldValue(state, CurrentModuleObject, fields, objectSchemaProps, "air_mass_flow_coefficient_at_reference_conditions", coeff);

std::string coeff = "look a coefficient!";
state.dataInputProcessing->inputProcessor->getFieldValue(state, CurrentModuleObject, fields, objectSchemaProps, "air_mass_flow_coefficient_at_reference_conditions", coeff);

This means there is only ever one function name to call.
Otherwise, you'll need to have getRealFieldValue, getIntFieldValue, and getAlphaFieldValue functions like below:

Real64 coeff = state.dataInputProcessing->inputProcessor->getRealFieldValue(state, CurrentModuleObject, fields, objectSchemaProps, "air_mass_flow_coefficient_at_reference_conditions");

// Load the material derived type from the input data.
state.dataMaterial->Material(MaterNum).Group = RegularMaterial;
state.dataMaterial->Material(MaterNum).Name = materialName;

Copy link
Collaborator

Choose a reason for hiding this comment

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

Just a stylistic thing, but defining auto & Mat = state.dataMaterial->Material(MaterNum); and auto & ip = state.dataInputProcessing->inputProcessor; will make the rest of statements in this function into one liners.


std::string roughness = state.dataInputProcessing->inputProcessor->getAlphaFieldValue(
state, state.dataHeatBalMgr->CurrentModuleObject, objectFields, objectSchemaProps, "roughness");
ValidateMaterialRoughness(state, MaterNum, roughness, ErrorsFound);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not for this PR, but just noting that ValidateMaterialRoughness and functions of similar ilk will be going soon.

} else {
bool is_empty = field_value.get<std::string>().empty();
if (is_empty) {
isDefaulted = findDefault(value, schema_field_obj);
Copy link
Collaborator

@amirroth amirroth May 25, 2021

Choose a reason for hiding this comment

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

Need to do something if isDefaulted is false?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another good question. This block of code is borrowed from elsewhere (somewhere in the getObjectItem chain. isDefaulted gets used elsewhere for some statistics on the number of defaulted fields in the input file. This could probably be simplified a bit here.

@mjwitte mjwitte requested a review from mbadams5 May 26, 2021 18:24
}

Real64 InputProcessor::getRealFieldValue(
EnergyPlusData &state, std::string const &objectWord, json const &ep_object, json const &schema_obj_props, std::string const &fieldName)
Copy link
Contributor

Choose a reason for hiding this comment

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

If you wanted to return int or Real, you would need to have separate functions since they'd likely have the same input parameter signature and only change the return type.

}

Real64 InputProcessor::getRealFieldValue(
EnergyPlusData &state, std::string const &objectWord, json const &ep_object, json const &schema_obj_props, std::string const &fieldName)
Copy link
Contributor

Choose a reason for hiding this comment

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

I will also say with the following, which I saw in an outdated comment in this PR:

Real64 coeff = 0.0;
state.dataInputProcessing->inputProcessor->getFieldValue(state, CurrentModuleObject, fields, objectSchemaProps, "air_mass_flow_coefficient_at_reference_conditions", coeff);

It does seem odd to not directly return the fieldValue, however, this function could be overloaded to include int or string as input types with their own unique functions. Such as below:

int coeff = 0.0;
state.dataInputProcessing->inputProcessor->getFieldValue(state, CurrentModuleObject, fields, objectSchemaProps, "air_mass_flow_coefficient_at_reference_conditions", coeff);

std::string coeff = "look a coefficient!";
state.dataInputProcessing->inputProcessor->getFieldValue(state, CurrentModuleObject, fields, objectSchemaProps, "air_mass_flow_coefficient_at_reference_conditions", coeff);

This means there is only ever one function name to call.
Otherwise, you'll need to have getRealFieldValue, getIntFieldValue, and getAlphaFieldValue functions like below:

Real64 coeff = state.dataInputProcessing->inputProcessor->getRealFieldValue(state, CurrentModuleObject, fields, objectSchemaProps, "air_mass_flow_coefficient_at_reference_conditions");

return value;
}

json InputProcessor::getObjectSchemaProps(EnergyPlusData &state, std::string const &objectWord)
Copy link
Contributor

Choose a reason for hiding this comment

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

This will create a copy of the schema json everytime it is called. It should really return json const &, especially since getPatternProperties returns json const &:

json const &InputProcessor::getPatternProperties(EnergyPlusData &state, json const &schema_obj)

and you return from getPatternProperties

json schema_obj_props = getPatternProperties(state, object_schema);
return schema_obj_props;

If this current function is called many times it will cause performance issues.

@mjwitte
Copy link
Contributor Author

mjwitte commented Jun 9, 2021

Otherwise, you'll need to have getRealFieldValue, getIntFieldValue, and getAlphaFieldValue functions like below:

@mbadams5 Is there anywhere in the current input processing that distinguishes between int and real? getObjectItem return an array of reals, regardless of the field type in the idd and the schema seems to just have alpha or numeric. So, I'm planning to change getRealFieldValue to getNumericFieldValue unless I'm missing something related to integer fields.

@mbadams5
Copy link
Contributor

@mjwitte No, but mostly that is an artifact of the old InputProcessor and IDD. The IDD does not distinguish between real and int, however, json schema can use integer type for fields that should be integer. So as long as we are auto-generating the epjson schema from the IDD, it will likely only ever be alpha or numeric. The same goes for internally within InputProcessor, but there could be a future where there are expanded types usable by epjson schema.

@mjwitte
Copy link
Contributor Author

mjwitte commented Jun 10, 2021

@mbadams5 Actually, there are some fields in the IDD that are tagged with \type integer so we could be using that information in the epJSON schema. I'll add an integer function here for now and then in the future the real and integer functions can validate against the numeric type.

I think I've addressed your const & comment in getObjectSchemaProps. Please confirm that I got it right.

std::string InputProcessor::getAlphaFieldValue(json const &ep_object, json const &schema_obj_props, std::string const &fieldName)
{
auto const &schema_field_obj = schema_obj_props[fieldName];
assert(!schema_field_obj.empty()); // Check that field name exists in the schema for this object type
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you convert this to an assert statement instead of the FatalErrorMessage? Asserts only work in debug builds, so is this something only developers will ever hit? If not, then it should probably be the FatalError.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed to assert because this is a developer error (same for the others). The function arguments for state and objectType were only being used for these error messages that should never happen unless the developer makes a mistake. So it seemed reasonable to use an assert and drop those.

Copy link
Contributor

Choose a reason for hiding this comment

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

@mjwitte can you give me an example of why schema_field_obj.empty() would ever be false? I get the misspelled field name issue but are there other instances? Inputs past min-fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see this is merged, but just for the record, this line is looking at the schema (not the user input) in schema_obj_props. So empty will be true if the field name does not exist in schema_obj_props.

value = valuePair.first;
isDefaulted = valuePair.second;
} else {
assert(false); // String value requested but field type in numeric
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment about change to assert here. This assert statement provides little information except for the inline comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can revert this, but I like the leaner function signature. If there's a better approach, I'm open to suggestions.

Real64 InputProcessor::getRealFieldValue(json const &ep_object, json const &schema_obj_props, std::string const &fieldName)
{
auto const &schema_field_obj = schema_obj_props[fieldName];
assert(!schema_field_obj.empty()); // Check that field name exists in the schema for this object type
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question about change to assert.

{
auto const &schema_properties = schema.at("properties");
const json &object_schema = schema_properties.at(objectWord);
assert(!object_schema.empty()); // If this fails, the object type does not exist in the schema
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question about change to assert.

@mbadams5
Copy link
Contributor

@mjwitte I see in the IDD where it has \type integer and I also see in idd_parser.py script where it finds it but only uses number for both real and int. In the future it seems easy to break these apart to include a type integer for epjson schema.

The getObjectSchemaProps change looks correct.

@rraustad
Copy link
Contributor

@mjwitte and @mbadams5 (thanks for the review) are there any remaining issues? I would like this merged as soon as possible. I am available to click merge at a moments notice.

@rraustad
Copy link
Contributor

Unit tests run to completion on Win64. This seems ready and any remaining items can be addressed as needed with follow up issues. I am a little surprised there is an indication of a speed improvement albeit small. I will wait on CI for a bit.

Copy link
Contributor

@rraustad rraustad left a comment

Choose a reason for hiding this comment

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

I see no significant issues. I am not sure int vs real functions is even an issue.

@rraustad
Copy link
Contributor

rraustad commented Jun 12, 2021

Failed unit test PackagedTerminalHP_VSCoils_Sizing on Mac is likely unrelated.

@rraustad rraustad merged commit 5358d3e into develop Jun 12, 2021
@rraustad rraustad deleted the epJSONhelpers branch June 12, 2021 00:43
@rraustad rraustad mentioned this pull request May 13, 2022
14 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Refactoring Includes code changes that don't change the functionality of the program, just perform refactoring
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants