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

DataCollector: Allow agent reporters to take class methods and functions with parameter lists #1838

Merged
merged 5 commits into from
Oct 20, 2023

Conversation

EwoutH
Copy link
Member

@EwoutH EwoutH commented Oct 18, 2023

Modify the DataCollector class to allow agent reporters to take methods of a class/instance and functions with parameters placed in a list (like model reporters), by extending the _new_agent_reporter method.

This implementation starts by checking if the reporter is an attribute string. If so, it creates a function to retrieve the attribute from an agent. Next, it checks if the reporter is a list. If it is, this indicates that we have a function with parameters, so it wraps that function to pass those parameters when called.

For any other type (like lambdas or methods), we assume they're directly suitable to be used as reporters.

Now, with this modification, agent reporters in the DataCollector class can take:

  1. Attribute strings
  2. Function objects (like lambdas)
  3. Methods of a class/instance
  4. Functions with parameters placed in a list

This approach ensures backward compatibility because the existing checks for attribute strings and function objects remain unchanged. The added functionality only extends the capabilities of the class without altering the existing behavior.

Closes: #1837

Todo

  • Update and fix tests
  • Add more docstring

Can be done in separate PRs:

  • Update examples
  • Update tutorial (optional)

…ons with parameter lists

Modify the `DataCollector` class to allow agent reporters to take methods of a class/instance and functions with parameters placed in a list (like model reporters), by extending the `_new_agent_reporter` method.

This implementation starts by checking if the reporter is an attribute string. If so, it creates a function to retrieve the attribute from an agent. Next, it checks if the reporter is a list. If it is, this indicates that we have a function with parameters, so it wraps that function to pass those parameters when called.

For any other type (like lambdas or methods), we assume they're directly suitable to be used as reporters.

Now, with this modification, agent reporters in the `DataCollector` class can take:

1. Attribute strings
2. Function objects (like lambdas)
3. Methods of a class/instance
4. Functions with parameters placed in a list

This approach ensures backward compatibility because the existing checks for attribute strings and function objects remain unchanged. The added functionality only extends the capabilities of the class without altering the existing behavior.
@rht
Copy link
Contributor

rht commented Oct 18, 2023

Is this a syntax sugar for an agent reporter of previously specified as lambda agent: func(agent, *params), but now with [func, params]?

_new_agent_reporter wasn't intended into the DataCollector class, so thus not seen as a method.
@codecov
Copy link

codecov bot commented Oct 19, 2023

Codecov Report

All modified lines are covered by tests ✅

Comparison is base (d40bc5b) 79.01% compared to head (0fe24d7) 79.12%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1838      +/-   ##
==========================================
+ Coverage   79.01%   79.12%   +0.11%     
==========================================
  Files          15       15              
  Lines         910      915       +5     
  Branches      193      194       +1     
==========================================
+ Hits          719      724       +5     
  Misses        168      168              
  Partials       23       23              
Files Coverage Δ
mesa/datacollection.py 90.24% <100.00%> (+0.63%) ⬆️

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@EwoutH EwoutH force-pushed the agent-reporter-datacollector branch from 70edb80 to 2a266b2 Compare October 19, 2023 12:29
- Move agent_reporters specification into initialize_data_collector()
- Add back the tables argument (accidentally deleted in previous commit)
- Use parentheses to parse step and agent_id from agent records dataframe, since those are the multi-index key
- Update expected values for new agent reporter types
- Update length values of new agent table and vars (both increase by 2 due to 2 new agent reporter columns)
@EwoutH EwoutH force-pushed the agent-reporter-datacollector branch from 2a266b2 to 05d1767 Compare October 19, 2023 12:31
@EwoutH
Copy link
Member Author

EwoutH commented Oct 19, 2023

Code is ready to review. Will still work a bit on documentation.

Is this a syntax sugar for an agent reporter of previously specified as lambda agent: func(agent, *params), but now with [func, params]?

Yes, that's completely right, the modification is essentially syntactic sugar, offering an alternative way to specify agent reporters that involve functions with additional parameters.

The big advantage of this however, is that it's now completely consistent with specifying model reporters. This way, if you can use the syntax for a model reporter, you can use it for an agent reporter, and visa versa.

A disadvantage could of course be the increased complexity of the code base. But, then I would argue, we should also report one of those syntax types from the model reporter. Because than that would be consistent, but I also don't think that's a desired option due to breaking backwards compatibility.

@EwoutH EwoutH marked this pull request as ready for review October 19, 2023 12:55
@EwoutH
Copy link
Member Author

EwoutH commented Oct 19, 2023

Updated the docs, this PR is ready for review.

I might update the tutorial and some examples in separate PRs.

@rht
Copy link
Contributor

rht commented Oct 19, 2023

I personally think that passing lambda agent: func(agent, *params) doesn't require you to read extra documentation and is most likely what people would have come up with naturally.

I think it has nothing to do with the pickling issue in multiprocessing as I initially guessed in #1837 (comment), because it will create a local anonymous function that is unpickleable anyway.

@jackiekazil @tpike3 should we merge this PR?

return getattr(agent, attribute_name, None)

reporter.attribute_name = attribute_name
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to be a bug introduced in bc5a5cd, and you fixed in this PR.

@rht
Copy link
Contributor

rht commented Oct 19, 2023

Content: LGTM. Concept: I prefer passing lambda agent: func(agent, *params), but others might think otherwise.

@jackiekazil
Copy link
Member

I am good with this. @rht thank you for doing the review.
LGTM.

@jackiekazil jackiekazil merged commit 6616cda into projectmesa:main Oct 20, 2023
11 checks passed
@EwoutH
Copy link
Member Author

EwoutH commented Oct 20, 2023

Thanks for reviewing and merging!

@EwoutH
Copy link
Member Author

EwoutH commented Oct 24, 2023

Thinking about it a bit longer I think I just found quite a serious limitation to this approach. Take the class method syntax:

agent_reporters={
                "double_value": MockAgent.double_val,
            },

This works perfectly fine for models with one Agent class, but what about models with multiple agent types? We can't use self in the Agent-reporter, since that would refer to the Model, right?

We should consider this syntax and how to handle it in #348 #1142 #1419 #1701 #1702 or wherever our current multiple agent-type datacollection discussion is taking place.

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.

Document possible agent reporter inputs in the datacollector
3 participants