Skip to content

ENH: Accept Scenario as class or instance in Model init#3450

Closed
Nithurshen wants to merge 4 commits intomesa:mainfrom
Nithurshen:enh/scenario-class-support
Closed

ENH: Accept Scenario as class or instance in Model init#3450
Nithurshen wants to merge 4 commits intomesa:mainfrom
Nithurshen:enh/scenario-class-support

Conversation

@Nithurshen
Copy link
Copy Markdown
Contributor

Pre-PR Checklist

  • I linked an issue/discussion where a maintainer approved this enhancement/feature for implementation.

Approval Link

Closes #3435

Summary

This PR updates the Model class initialization to accept a Scenario class directly, automatically instantiating it with the appropriate default or provided rng. It maintains full backwards compatibility by continuing to support pre-instantiated Scenario objects or None.

Motive

The current pattern for wiring Scenarios into Models creates unnecessary boilerplate code in every model subclass (e.g., manually checking if scenario is None:). By allowing a Scenario class to be passed directly, developers can use the class as a default argument in their model's signature. This improves code readability, enables proper IDE autocomplete, and allows tools like SolaraViz or batch_run to easily inspect default parameters.

Implementation

  1. mesa/model.py:
  • Updated the Model.__init__ type hints to accept a Scenario class (scenario: S | type[S] | None = None).
  • Modified the initialization logic to check if scenario is a class (isinstance(scenario, type) and issubclass(scenario, Scenario)). If so, it instantiates the scenario using the resolved rng.
  • It continues to safely handle pre-existing Scenario instances and None fallbacks.
  1. tests/test_model.py: Added a new test, test_model_scenario_initialization, which verifies the behavior when passing None, a pre-configured Scenario instance, and a custom Scenario class.

Usage Examples

Before:

class PdGrid(mesa.Model):
    def __init__(self, scenario=None):
        if scenario is None:
            scenario = PrisonersDilemmaScenario()
        super().__init__(scenario=scenario)

After:

class PdGrid(mesa.Model):
    def __init__(self, scenario=PrisonersDilemmaScenario, rng=None):
        super().__init__(scenario=scenario, rng=rng)
        
model = PdGrid()
model = PdGrid(rng=42)
model = PdGrid(scenario=PrisonersDilemmaScenario(width=100, rng=42))

Additional Notes

All existing tests and the newly added test_model_scenario_initialization pass locally.

if scenario is not None:
if rng is not None and (scenario.rng != rng):
# Handle scenario if it is a class, instance, or None
if isinstance(scenario, type) and issubclass(scenario, Scenario):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Did we not agree to raise probably a TypeError if scenario is not a subclass?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

At some point you said:

I don't want to force users to subclass Scenario to make any of this work.

It that still your current view?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Both can be true: use the default Scenario class for just the rng and pass the rest as keyword arguments. But yes, it's a fair question. Looking at the open PRs on simplifying the examples, I like the cleaner code with scenario subclasses.

Comment on lines +142 to +145
# It's an instance, check if rng matches
if rng is not None and scenario.rng != rng:
raise ValueError("rng and scenario.rng must be the same")
else:
rng = scenario.rng
rng = scenario.rng
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@quaquel how would you like to handle this long term? Always put rng in Scenario, keep it as something separate, something else?

Copy link
Copy Markdown
Member

@quaquel quaquel Mar 6, 2026

Choose a reason for hiding this comment

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

Always put it in Scenario.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So we can deprecate/remove inputting rng directly? Only allow rng to be passed as part of a Scenario?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yes, but keep that out of this PR. I hope to write up some stuff on replacing batchrunner soon and this will be included in that.

@Nithurshen Nithurshen requested review from EwoutH and quaquel March 9, 2026 08:52
@EwoutH
Copy link
Copy Markdown
Member

EwoutH commented Mar 9, 2026

Thanks for the effort.

@quaquel indicated that he wanted to give a stab to solve this more integrally, including resolving rng handling. If that's still his intention, I would prefer waiting how that would develop.

@quaquel
Copy link
Copy Markdown
Member

quaquel commented Mar 10, 2026

closing this in favor of #3493

@quaquel quaquel closed this Mar 10, 2026
@Nithurshen Nithurshen deleted the enh/scenario-class-support branch March 10, 2026 14:23
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.

Accept Scenario as class or instance in Model init

3 participants