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

Convert int constexpr to enum : EconomicLifeCycleCost.* #9170

Merged
merged 52 commits into from
Nov 18, 2021
Merged

Conversation

jmythms
Copy link
Contributor

@jmythms jmythms commented Nov 5, 2021

Pull request overview

Convert economic life cycle cost categories from int constexpr to enum.

I used plain enums in this PR so that I did not have to use static_cast.

Explaination for Table diffs:
The rows just got rearranged when I rearranged the enums. Rearranged the enums so I could use loops instead of unrolled code for generating the tables. Screenshot from the ABUPS (.HTML) file generated when using the 5ZoneEconomicsTariffAndLifeCycleCosts.idf file:
image

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

@jmythms jmythms added the Refactoring Includes code changes that don't change the functionality of the program, just perform refactoring label Nov 5, 2021
@jmythms jmythms added this to the EnergyPlus 2022.1 milestone Nov 5, 2021
@jmythms jmythms self-assigned this Nov 5, 2021
Comment on lines 464 to 470
for (iInObj = 0; iInObj < elcc->numRecurringCosts; ++iInObj) {
state.dataInputProcessing->inputProcessor->getObjectItem(state,
CurrentModuleObject,
iInObj,
iInObj+1,
AlphaArray,
NumAlphas,
NumArray,
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 had to use +1 for the third argument in the getObjectItem() function so the vector does not go out of bounds.

Comment on lines +1378 to +1389
elcc->CashFlow[CostCategory::TotEnergy].mnAmount(jMonth) = elcc->CashFlow[CostCategory::Energy].mnAmount(jMonth);
elcc->CashFlow[CostCategory::TotOper].mnAmount(jMonth) =
elcc->CashFlow[CostCategory::Maintenance].mnAmount(jMonth) + elcc->CashFlow[CostCategory::Repair].mnAmount(jMonth) +
elcc->CashFlow[CostCategory::Operation].mnAmount(jMonth) + elcc->CashFlow[CostCategory::Replacement].mnAmount(jMonth) +
elcc->CashFlow[CostCategory::MinorOverhaul].mnAmount(jMonth) + elcc->CashFlow[CostCategory::MajorOverhaul].mnAmount(jMonth) +
elcc->CashFlow[CostCategory::OtherOperational].mnAmount(jMonth) + elcc->CashFlow[CostCategory::Water].mnAmount(jMonth) +
elcc->CashFlow[CostCategory::Energy].mnAmount(jMonth);
elcc->CashFlow[CostCategory::TotCaptl].mnAmount(jMonth) = elcc->CashFlow[CostCategory::Construction].mnAmount(jMonth) +
elcc->CashFlow[CostCategory::Salvage].mnAmount(jMonth) +
elcc->CashFlow[CostCategory::OtherCapital].mnAmount(jMonth);
elcc->CashFlow[CostCategory::TotGrand].mnAmount(jMonth) =
elcc->CashFlow[CostCategory::TotOper].mnAmount(jMonth) + elcc->CashFlow[CostCategory::TotCaptl].mnAmount(jMonth);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Enums start at 0 so converted elcc->CashFlow from EPVector to std::vector.

Copy link
Collaborator

Choose a reason for hiding this comment

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

If CashFlow is really indexed by an enum then you can convert it to std::array. This applies to all arrays indexed by enum.

Copy link
Contributor Author

@jmythms jmythms Nov 9, 2021

Choose a reason for hiding this comment

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

Only part of CashFlow is indexed by an enum... It is more of a std::vector/Array1D like container that can change size based on the number of items in the IDF.

More details:
As shown in this line, numCashFlow is the size of CashFlow, :
elcc->numCashFlow = countOfCostCat + elcc->numRecurringCosts + elcc->numNonrecurringCost + elcc->numResourcesUsed;.
In that equation, countOfCostCat is always constant and equal to the number of enum values, and that part of CashFlow is indexed by the enum. The other terms (numRecurringCosts, numNonrecurringCost, numResourcesUsed) depend on the IDF.

Should I pull out the countOfCostCat part and convert that to std::array, and then continue down that path?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see, thanks for that clarification. As you were.

@amirroth
Copy link
Collaborator

amirroth commented Nov 6, 2021

Is it worthwhile making comments on this module since it is tagged for deprecation?

@mjwitte
Copy link
Contributor

mjwitte commented Nov 6, 2021

I'm pretty sure we discussed deprecating ComponentCost:*, but not LifeCycleCost:*

Energy,
NonEnergy,
NotComputed,
Num
};

constexpr const char *MonthNames(int const &i)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This thing can be a constexpr std::array<std::string view, 12> lookup, it shouldn't be a function. It should also probably be in a central utility or calendar module.

@@ -223,13 +223,13 @@ void GetInputLifeCycleCostParameters(EnergyPlusData &state)
// \key BeginningOfYear
// \default EndOfYear
if (UtilityRoutines::SameString(AlphaArray(2), "EndOfYear")) {
elcc->discountConvention = iDiscConv::EndOfYear;
elcc->discountConvention = DiscConv::EndOfYear;
Copy link
Collaborator

Choose a reason for hiding this comment

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

getEnumerationValue

@@ -390,33 +390,33 @@ void GetInputLifeCycleCostParameters(EnergyPlusData &state)
// \key None
// \default None
if (UtilityRoutines::SameString(AlphaArray(6), "ModifiedAcceleratedCostRecoverySystem-3year")) {
elcc->depreciationMethod = iDeprMethod::MACRS3;
elcc->depreciationMethod = DeprMethod::MACRS3;
Copy link
Collaborator

Choose a reason for hiding this comment

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

getEnumerationValue

@@ -498,39 +498,39 @@ void GetInputLifeCycleCostRecurringCosts(EnergyPlusData &state)
// \key OtherOperational
// \default Maintenance
if (UtilityRoutines::SameString(AlphaArray(2), "Maintenance")) {
elcc->RecurringCosts(iInObj).category = costCatMaintenance;
elcc->RecurringCosts[iInObj].category = CostCategory::Maintenance;
Copy link
Collaborator

Choose a reason for hiding this comment

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

getEnumerationValue

// A3, \field Start of Costs
// \type choice
// \key ServicePeriod
// \key BasePeriod
// \default ServicePeriod
if (UtilityRoutines::SameString(AlphaArray(3), "ServicePeriod")) {
elcc->RecurringCosts(iInObj).startOfCosts = iStartCosts::ServicePeriod;
elcc->RecurringCosts[iInObj].startOfCosts = iStartCosts::ServicePeriod;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same.

@@ -690,39 +690,39 @@ void GetInputLifeCycleCostNonrecurringCost(EnergyPlusData &state)
// A1, \field Name
// \required-field
// \type alpha
elcc->NonrecurringCost(iInObj).name = AlphaArray(1);
elcc->NonrecurringCost[iInObj].name = AlphaArray(1);
// A2, \field Category
// \type choice
// \key Construction
// \key Salvage
// \key OtherCapital
// \default Construction
if (UtilityRoutines::SameString(AlphaArray(2), "Construction")) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same.

ShowWarningError(state,
CurrentModuleObject + ": Invalid " + state.dataIPShortCut->cAlphaFieldNames(2) + "=\"" + AlphaArray(2) +
"\". The category of Construction will be used.");
}
// N1, \field Cost
// \type real
elcc->NonrecurringCost(iInObj).cost = NumArray(1);
elcc->NonrecurringCost[iInObj].cost = NumArray(1);
// A3, \field Start of Costs
// \type choice
// \key ServicePeriod
// \key BasePeriod
// \default ServicePeriod
if (UtilityRoutines::SameString(AlphaArray(3), "ServicePeriod")) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same.

Comment on lines +259 to +260
constexpr std::string_view Total{"Total"};
constexpr std::string_view TotalUC{"TOTAL"};
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 there a better way to do this?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe, but I'm not sure if it is worth it or not. C++ has a toupper function that operates on individual characters, but not on a string. So you'd have to iterate over the characters, and you'd need to construct a string while doing it. Maybe @lefticus has a fancy idea for how to do this most efficiently, but unless this is a widespread issue on this PR, I'd just put a pin in it. Getting it into constexpr string_view is already such an improvement over the past...

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is the fastest and cheapest way to do this unless the MakeUpper function can itself be made constexpr. I don't think it can, but it may be worth a quick test. Even then the implementation wouldn't be cheaper, it would have the same cost but maybe the code would be a bit clearer. Either way, it's not worth holding up this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the inputs and insights.
No, it's not a widespread issue, just felt that it felt a bit weird pulling these out as individual variables.

Will investigate changing MakeUpper into a constexpr function separately.

Copy link
Member

Choose a reason for hiding this comment

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

https://godbolt.org/z/nKcEf5eev I've given it 10 minutes and haven't figured out a path forward on it. I assume that means it is not possible. 😆 🤣 JK, but for real that's a starting point if anyone wants to try to make a clean solution.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Where did you learn how to program?

Copy link
Collaborator

Choose a reason for hiding this comment

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

More seriously, makeUpperCase cannot be made constexpr because it has to allocate memory for the target string so this path is closed. Just leave as is for now.

Copy link
Member

@Myoldmopar Myoldmopar left a comment

Choose a reason for hiding this comment

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

This is really great. I don't see any red flags here, this is just tons of really great cleanup. I will do some final testing locally but as far as I can tell this is good to go in. If anyone else wants any specific tests/demos/changes, speak up soon.

ShowWarningError(state,
CurrentModuleObject + ": Invalid " + state.dataIPShortCut->cAlphaFieldNames(6) + "=\"" + AlphaArray(6) +
R"(". "None" will be used.)");
elcc->depreciationMethod = static_cast<DeprMethod>(getEnumerationValue(DeprMethodNamesUC, UtilityRoutines::MakeUPPERCase(AlphaArray(6))));
Copy link
Member

Choose a reason for hiding this comment

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

@mjwitte maybe you can remind me here...can we not count on the input being all caps at this point? Assuming we stick with a case-insensitive input format, even for epJSON values, we should just make sure the input processor upper cases everything, even the epJSON blob. (Of course not the fields with \retaincase).

It would be really nice to just know for sure that the AlphaArray was already upper case then we could eliminate so many MakeUPPERCase function calls and clean up so many chunks of code.

Copy link
Contributor

Choose a reason for hiding this comment

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

At present, there's no global pass to push the input strings to UPPERcase. getObjectItem and getAlphaFieldValue return UPPERcase strings. Direct access of the JSON data does not.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think that is strictly necessary. There is nothing wrong with makeUpperCase as long as you only do it once per string. The most important thing is to avoid case-insensitive comparisons and there are other ways to do that. See this issue.

Copy link
Member

Choose a reason for hiding this comment

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

That's what I was afraid of, but I also see a good possibility there. If the IP just makes one quick pass to upper case all the (non-retain-case) fields in the IDF or epJSON, then we would get rid of a huge amount of these MakeUPPERCase calls spread out all over the code. And also it would make it perfectly clear to the developers that the data will be upper case as soon as the input processor is done with it. No ambiguity that leads to extraneous string operations during input processing because the dev isn't sure if it will be upper case or not.

I think this would be a great discussion to have, not here, but soon. Thanks!

Copy link
Member

Choose a reason for hiding this comment

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

Thanks @amirroth, I think I am in full agreement that there is nothing wrong with MakeUPPERCase, as long as it is only done once, but the problem is that we aren't quite sure if it's been done or not, so it ends up being done extra times. If the input processor just made it upper case one time then we could be absolutely sure it's upper case and eliminate many MakeUPPERCase calls spread out of the code. Anyway, nothing for this PR. Thanks!

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree. If MakeUpperCase is done in input processor by default that's even better, but what I was saying is that we can eliminate a lot of the expensive case-insensitive comparisons even with no changes to the input processor or JSON module, because the problem is not MakeUpperCase inherently, it's the fact that SameString essentially does a MakeUpperCase on both inputs and it is called by many search routines.

elcc->NonrecurringCost(iInObj).category = costCatOtherCapital;
} else {
elcc->NonrecurringCost(iInObj).category = costCatConstruction;
elcc->NonrecurringCost[iInObj].category =
Copy link
Member

Choose a reason for hiding this comment

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

All these changes seem fine really. I definitely look forward to the BITF stuff becoming constexpr functions instead of macros.

@@ -303,7 +295,13 @@ void GetInputLifeCycleCostParameters(EnergyPlusData &state)
// \key November
// \key December
// \default January
elcc->baseDateMonth = MonthToMonthNumber(AlphaArray(4), 1);
elcc->baseDateMonth = getEnumerationValue(UtilityRoutines::MonthNamesUC, UtilityRoutines::MakeUPPERCase(AlphaArray(4)));
Copy link
Member

Choose a reason for hiding this comment

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

Nice! So much cleaner, and another function gone.

EXPECT_EQ(9, getEnumerationValue(UtilityRoutines::MonthNamesUC, UtilityRoutines::MakeUPPERCase("October")));
EXPECT_EQ(10, getEnumerationValue(UtilityRoutines::MonthNamesUC, UtilityRoutines::MakeUPPERCase("November")));
EXPECT_EQ(11, getEnumerationValue(UtilityRoutines::MonthNamesUC, UtilityRoutines::MakeUPPERCase("December")));
EXPECT_EQ(-1, getEnumerationValue(UtilityRoutines::MonthNamesUC, UtilityRoutines::MakeUPPERCase("Hexember")));
Copy link
Member

Choose a reason for hiding this comment

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

I probably would have just removed this unit test entirely. But it's fine, it's extra checking of the getEnumerationValue function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it, will remember next time!

offset = CostCategory::Num;
rowHead(jObj + 1) = elcc->CashFlow[offset + jObj].name;
switch (elcc->CashFlow[offset + jObj].Category) {
case (CostCategory::Maintenance):
Copy link
Member

Choose a reason for hiding this comment

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

No need to put parentheses around the case labels. No need to hold it up, but in the future don't add them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it. Happened because I memorized this pattern. Will not add them in the future.
image


int constexpr SizeDepr(41);

constexpr std::array<std::array<Real64, SizeDepr>, static_cast<int>(DeprMethod::Num)> DepreciationPercentTable{
Copy link
Member

Choose a reason for hiding this comment

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

This isn't the easiest thing to read/maintain, but I think it's a net gain. The huge IF block, non-constexpr, assignments weren't great either. Maybe there is a better way to format the tabular data ... I dunno. No need to address it now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would it be better to split stuff up to improve readability? Something like,

constexpr std::array<string_view> smallTable1;
constexpr std::array<string_view> smallTable2;

constexpr std::array<std:array> bigTable {smallTable1, smallTable2};

rowHead(14) = "Total Operation";
rowHead(15) = "Total Capital";
rowHead(16) = "Grand Total";
for (int CashFlowCostCategory = CostCategory::Maintenance; CashFlowCostCategory <= CostCategory::TotGrand; ++CashFlowCostCategory) {
Copy link
Member

Choose a reason for hiding this comment

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

I see, so this is why the table diffs appear. I think that is acceptable and these loops are much nicer than the manually listed out chunks of code.

@Myoldmopar
Copy link
Member

Merging in 10 minutes unless someone speaks up otherwise.

@Myoldmopar Myoldmopar merged commit 8e834f8 into develop Nov 18, 2021
@Myoldmopar Myoldmopar deleted the enumELCC branch November 18, 2021 17:27
@jmythms
Copy link
Contributor Author

jmythms commented Nov 18, 2021

Thanks so much!

yujiex pushed a commit that referenced this pull request Jan 27, 2022
Convert `int constexpr` to `enum` : EconomicLifeCycleCost.*
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