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

Support iterating with user-supplied callbacks #115

Merged
merged 10 commits into from
Apr 8, 2019

Conversation

khaeru
Copy link
Member

@khaeru khaeru commented Jan 23, 2019

This PR adds a feature to Scenario.solve() iteratively, with some user-supplied callback.

This is to generalize and support model variants like MESSAGE-Access and MESSAGE-Transport.

PR checklist:

  • Tests added
  • Documentation added
  • Description in RELEASE_NOTES.md added

@khaeru khaeru added the enh New features & functionality label Jan 23, 2019
@khaeru khaeru self-assigned this Jan 23, 2019
@khaeru khaeru mentioned this pull request Feb 18, 2019
3 tasks
@khaeru khaeru force-pushed the feature/solve-callback branch from df92afc to 0deff40 Compare March 19, 2019 13:44
tests/test_core.py Outdated Show resolved Hide resolved
tests/test_core.py Outdated Show resolved Hide resolved
@khaeru khaeru force-pushed the feature/solve-callback branch from ef42180 to e951b10 Compare April 1, 2019 19:37
@khaeru khaeru changed the title [WIP] Support iterating with user-supplied callbacks Support iterating with user-supplied callbacks Apr 1, 2019
@khaeru khaeru added this to the 0.2 milestone Apr 1, 2019
@gidden
Copy link
Member

gidden commented Apr 2, 2019

@MiguelPobleteCazenave would you mind taking a first pass in reviewing? Happy to do a second after the first round.

@MiguelPobleteCazenave
Copy link
Contributor

sure, i'll give it a look later today

Copy link
Contributor

@MiguelPobleteCazenave MiguelPobleteCazenave left a comment

Choose a reason for hiding this comment

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

Looks good to me, but I just have one question maybe thinking forward. Wouldn't be desirable to pass more than just one argument for the callback (in case you want to have different versions of the other models, e.g., different levels of access targeted, different regions or in transport, different types of vehicles targeted)?

@khaeru
Copy link
Member Author

khaeru commented Apr 3, 2019

Wouldn't be desirable to pass more than just one argument for the callback (in case you want to have different versions of the other models, e.g., different levels of access targeted, different regions or in transport, different types of vehicles targeted)?

Ah, good call. I will add this now.

The user can also do this in a bunch of other ways, because the callback function will usually be defined in the same scope where solve() is called:

myparam1 = 10.2

def cb(scenario):
    # Use a global variable
    global myparam1

    # Retrieve data stored with the callback
    myparam2 = cb.data['myparam2']

    # Read a file
    with open('myparam3.txt') as f:
        myparam3 = f.read()

    # do some calculations
    return convergence

# Store data with the callback after it has been defined
cb.data = {'myparam2': 23.5}

# Callback will have access to myparam[123]
scenario.solve(callback=cb)

@khaeru
Copy link
Member Author

khaeru commented Apr 3, 2019

@MiguelPobleteCazenave done

Copy link
Member

@gidden gidden left a comment

Choose a reason for hiding this comment

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

In general this looks great! One in-line comments in tests.

More broadly, my first thought is whether the callback being defined as a function is sufficient. It seems to me that there will likely need to be a decent amount of information based back (and perhaps forth) between the message model run and callback model run. As is, the iteration number can be queried directly by the callback.

Perhaps it is fine to keep as is for now, but my first thought would be to prefer more a class-type implementation, e.g.,

class MyCallBack:

    def __init__(self, scenario, **kwargs):
        self.scenario = scenario

     def __call__(self, **kwargs):
         # do whatever is needed here

But I think this is not mutually exclusive to the current implementation, more a question of what type of implementation we suggest to users..


# Warning is raised because 'return False' is commented above, meaning
# user may have forgotten any return statement in the callback
message = (r'solve\(callback=...\) argument returned None; will loop '
Copy link
Member

Choose a reason for hiding this comment

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

This seems to be checking that the message is the same as that defined in core.py. Is it worth making this a top-level defined string there, e.g., CALLBACK_WARNING=.. and here setting message = core.CALLBACK_WARNING? As is, it seems like if someone later edits the callback warning without touching this code as well, the test may not behave as expected.

@gidden
Copy link
Member

gidden commented Apr 8, 2019

Per our in person conversation, we will leave test as is and let future devs deal with it if it becomes an issue.

@gidden gidden merged commit 88edf76 into iiasa:master Apr 8, 2019
@khaeru khaeru deleted the feature/solve-callback branch April 8, 2019 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enh New features & functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants