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 validity checking for non-behavior assumption parameters #1992

Merged
merged 7 commits into from
May 9, 2018
Merged

Add validity checking for non-behavior assumption parameters #1992

merged 7 commits into from
May 9, 2018

Conversation

martinholmer
Copy link
Collaborator

@martinholmer martinholmer commented May 7, 2018

This pull request moves the two Behavior._validate_* methods to the ParametersBase class and then uses them to add validity checking for consumption, growdiff_baseline, and growdiff_response parameters.

@martinholmer martinholmer changed the title Add validity-checking for non-behavior assumption parameters Add validity checking for non-behavior assumption parameters May 7, 2018
@codecov-io
Copy link

codecov-io commented May 7, 2018

Codecov Report

Merging #1992 into master will not change coverage.
The diff coverage is 100%.

Impacted file tree graph

@@          Coverage Diff           @@
##           master   #1992   +/-   ##
======================================
  Coverage     100%    100%           
======================================
  Files          38      38           
  Lines        3756    3808   +52     
======================================
+ Hits         3756    3808   +52
Impacted Files Coverage Δ
taxcalc/calculate.py 100% <100%> (ø) ⬆️
taxcalc/growdiff.py 100% <100%> (ø) ⬆️
taxcalc/parameters.py 100% <100%> (ø) ⬆️
taxcalc/behavior.py 100% <100%> (ø) ⬆️
taxcalc/consumption.py 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 29e760c...8c5f1b3. Read the comment docs.

@martinholmer martinholmer added ready and removed WIP labels May 7, 2018
# raise error if specifying both behavior and growdiff_response
if behv_dict and gdiff_resp_dict:
msg = 'both behavior and growdiff_response are specified'
raise ValueError('ERROR: ' + msg + '\n')
Copy link
Collaborator

Choose a reason for hiding this comment

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

@martinholmer do you think this type of integrated test is a rare situation? That is, do you think there will be more tests or validation checks that need to know information about the parameters specified for different TC classes (Policy, Behavior, Consumption, etc.)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, I don't think it is that rare a situation. This seems to be what came up in Tax-Calculator issue #1981. There will be a few more when I add the Growthmodel class and its parameters in the Growmod class.
Seems as if TaxBrain needs to put its call to Calculator.read_json_param_objects in a try statement.

Copy link
Collaborator

Choose a reason for hiding this comment

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

No, I don't think it is that rare a situation. This seems to be what came up in Tax-Calculator issue #1981. There will be a few more when I add the Growthmodel class and its parameters in the Growmod class.

Ok, thanks.

Seems as if TaxBrain needs to put its call to Calculator.read_json_param_objects in a try statement.

Sure, that would work.

What do you think about adding a method to the Calculator class that does a validation test before increment_year is called or on initialization? That way, the read_json_params_objects focuses on reading the JSON files and the validation is mostly done elsewhere. This method could look something like:

@staticmethod
def integrated_validation(pol, behv, growdiff_resp, growdiff_base):
    assert not (growdiff_resp.has_any_response() and behv.has_any_response())

    ... do some more tests on the objects here

Note: you can find a similar check in tbi.tbi_utils.calculate:

    # always prevent both behavioral response and growdiff response
    if behv.has_any_response() and growdiff_response.has_any_response():
        msg = 'BOTH behavior AND growdiff_response HAVE RESPONSE'
        raise ValueError(msg)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@hdoupe said:

What do you think about adding a method to the Calculator class that does a validation test before increment_year is called or on initialization? That way, the read_json_params_objects [function] focuses on reading the JSON files and the validation is mostly done elsewhere.

That was my first thought, but the Calculator object does not include any growdiff information.
I couldn't see an easy way around that problem. Did I miss an easy solution?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ahh, I see. So, the Growdiff information is applied to Growfactors, which is used by Policy and Records. Then, by the time, the Calculator class is initialized, the growdiff information is impossible to access. Is that right?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Well, I have one idea. What do you think about propagating the Growdiff.has_any_response up to Growfactors when the Growdiff.apply_to method is called? Then, it could be accessed from self.__policy._gfactors.has_growdiff_response. It's not the most elegant solution but it could work.

Plus, integrated_validation could be called from reform_warnings_errors since Growfactors, Policy, Consump, and Behavior are all available.

This approach could look like:

# growdiff class
    def update(self, name, year, diff):
        """
        Add to self.gfdf[name][year] the specified diff amount.
        """
        if self.used:
            msg = 'cannot update growfactors after they have been used'
            raise ValueError(msg)
       
        if diff != 0: # logically equivalent to Growdiff.has_any_response
            self.has_growdiff_response = True

        self.gfdf[name][year] += diff
# Calculator class
    def calc_all(self, zero_out_calc_vars=False):
        """
        Call all tax-calculation functions for the current_year.
        """
        Calculator.integrated_validation(self.__policy, self.__behavior, self.__consumption,
                                         self.__policy._gfactors)
        # conducts static analysis of Calculator object for current_year
       
        ...

    @staticmethod
    def integrated_validation(pol, behv, consump, gfactors):
        assert not (gfactors.has_growdiff_response
                    and behv.has_any_response())

Copy link
Collaborator

Choose a reason for hiding this comment

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

@martinholmer what was your expensive approach? Or, was this it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@hdoupe said:

What do you think about propagating the Growdiff.has_any_response up to Growfactors when the Growdiff.apply_to method is called? Then, it could be accessed from self.__policy._gfactors.has_growdiff_response.

I see two sorts of problems with this approach:

  1. It defers the parameter validity checking until the calculation stage. I thought the whole point of the parameter validity checking capability we have built for TaxBrain is to catch problematic parameter values before calculation starts.

  2. It relies on the user calling the two apply_to calls correctly and differently. It is only the growdiff_response.apply_to call that needs "propagating"; not the growdiff_baseline.apply_to call. If a user is knowledgeable enough to do this correctly, then that user doesn't need the parameter validity checking. It's only the clueless Python programmers that need to help and I don't think we can rely on them to make the two apply_to calls correctly.

The only foolproof way to do this (as far as I can see) is to make the growdiff_baseline and growdiff_response objects two different classes. But, as I said before, that's a lot of work.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, I see. That was the only idea I had. I'll move the read_json_param_objects call into a try block.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thank you for providing careful and clear explanation for why this approach wouldn't work so well.

@hdoupe
Copy link
Collaborator

hdoupe commented May 9, 2018

@martinholmer This PR looks good to me.

@martinholmer
Copy link
Collaborator Author

@hdoupe, Thanks for the careful review of pull request #1992.

@martinholmer martinholmer merged commit f7ebf3e into PSLmodels:master May 9, 2018
@martinholmer martinholmer deleted the assump-validity branch May 9, 2018 13:37
@martinholmer
Copy link
Collaborator Author

@hdoupe, Note that pull request #1998 removes the aspect of pull request #1992 that created the possibility that the read_json_param_objects function could return an ERROR message regarding an improper user specification. After #1998 that is no longer than case, so TaxBrain will not have to put that function in a try block when TaxBrain moves to Tax-Calculator version 0.20.0.

@hdoupe
Copy link
Collaborator

hdoupe commented May 15, 2018

Great, thanks for the update @martinholmer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants