Skip to content

Update Epstein Civil Violence model to use the new experimental Scenario class#3167

Merged
quaquel merged 5 commits intomesa:mainfrom
EwoutH:agent_scenario_property
Feb 14, 2026
Merged

Update Epstein Civil Violence model to use the new experimental Scenario class#3167
quaquel merged 5 commits intomesa:mainfrom
EwoutH:agent_scenario_property

Conversation

@EwoutH
Copy link
Copy Markdown
Member

@EwoutH EwoutH commented Jan 17, 2026

This PR updates the Epstein Civil Violence model to use the new experimental Scenario class for parameter management.

The goal for me personally was to get some feel how our new Scenario class works in practice. My first impression is quite positive, it removes so much of the boilerplate of just passing variables.

It also led me to a few open questions:

  1. We now have default parameters. How do we want to handle that? I now chose a default_scenario, does that make sense?

  2. Is using a default_scenario, where whould it be defined? At module level, as a class attribute, a separate scenario.py, or somewhere else?

  3. Documentation patters: Is # Parameters accessed via model.scenario: x, y, z sufficient in docstrings, or should we be more explicit about each parameter's purpose?

  4. With the current "just create an instance", type hinting doesn't work:
    Screenshot_778
    Should we support/recommend subclassing for this? That might also make a more elegant way to define default values.

    Or could we make Scenario be a @dataclass to enable type-hinted subclasses without boilerplate?

       class EpsteinScenario(Scenario):
           citizen_density: float = 0.7
           cop_vision: int = 7
           # ... full IDE autocomplete and type checking

@EwoutH EwoutH requested a review from quaquel January 17, 2026 21:59
@EwoutH EwoutH added the example Changes the examples or adds to them. label Jan 17, 2026
@github-actions

This comment was marked as off-topic.

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 17, 2026

One issue (and why the tests are failing) is that when using a module-level default_scenario the same scenario instance gets reused across multiple model instantiations, causing failures when creating a second model.

The problem:

# model.py (at module level)
default_scenario = Scenario(...)  # Created once

# Later:
model1 = EpsteinCivilViolence(seed=42)  # scenario.model = model1, model1.running = True
model2 = EpsteinCivilViolence(seed=43)  # Tries to set scenario.model = model2
# ERROR: "Cannot change scenario parameters during model run"
# because scenario.model still points to model1 where running=True

Current workaround: Create a fresh scenario instance inside __init__ if none is provided:

def __init__(self, ..., scenario=None):
    if scenario is None:
        scenario = Scenario(rng=seed, citizen_density=0.7, ...)
    super().__init__(seed=seed, scenario=scenario)

Subclassing might also help with this. Then we could have:

class EpsteinScenario(Scenario):
    citizen_density: float = 0.7
    cop_vision: int = 7
    # ... etc

And then either:

  • Users instantiate: EpsteinCivilViolence(scenario=EpsteinScenario(rng=42))
  • Or model creates internally: scenario = scenario or EpsteinScenario(rng=seed)

This gives us type hints, default values, and avoids the shared-instance problem.

(but maybe I'm using all this wrong)

@quaquel
Copy link
Copy Markdown
Member

quaquel commented Jan 18, 2026

One issue (and why the tests are failing) is that when using a module-level default_scenario the same scenario instance gets reused across multiple model instantiations, causing failures when creating a second model.

This is useful for stress-testing the design. In part, this interacts with #3166.

  1. The current design is not easy to combine with defaults. The simplest option would be to wrap the defaults in their own dict and then do Scenario(**defaults.copy().update(rng=43)). However, the use of defaults is so common that one might as well just have defined a scenario dataclass instead. Basically, this example is a strong counterargument to my dislike of forcing users to subclass.
  2. We might just remove the write protection.
  3. We might add a factory method, where you can do something like new.scenario = defaults.from(rng=42) which would return a copy of defaults with the fields that are passed updated.

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 18, 2026

Thanks. I'm curious what you think about #3168.

@quaquel
Copy link
Copy Markdown
Member

quaquel commented Jan 18, 2026

I assume this can be updated with #3168 merged.

While thinking about it some more, I am inclined to make scenarios frozen. That is, they simply cannot be changed once created. We can then remove the model attribute and the model.running check. This avoids subtle circular reference bugs.

@quaquel
Copy link
Copy Markdown
Member

quaquel commented Jan 18, 2026

I have made a bunch of updates. The bigger issue is that the UI side of things is not yet updated to use Scenario.

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 18, 2026

Ah, I also had some changes ready on my branch (part to develop and test #3168), I will check how they compare.

@EwoutH EwoutH force-pushed the agent_scenario_property branch 2 times, most recently from 8c54c93 to 404db2a Compare January 18, 2026 20:35
@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 18, 2026

I stored your work on agent_scenario_property_quaquel and pushed my branch. Do you want to compare them?

@quaquel
Copy link
Copy Markdown
Member

quaquel commented Jan 18, 2026

No, feel free to continue what you were doing. I was mainly curious to see how much #3168 helped.

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 18, 2026

In that case I think this PR is ready.

Viz indeed might need to be updated.

One question: I put the rng just in the scenario, do we regard that best practice from now on?

@quaquel
Copy link
Copy Markdown
Member

quaquel commented Jan 18, 2026

One question: I put the rng just in the scenario, do we regard that best practice from now on?

I don't know yet. It basically means that people are forced to use Scenario. But for Mesa 4, I think it is a good idea to do that anyway.

app.page # noqa: B018

model = EpsteinCivilViolence(rng=42)
model = EpsteinCivilViolence()
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.

it now defaults to rng=None?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No, rng = 42 is defined in EpsteinScenario, and if no Scenario is inputted, a default one is created:

if scenario is None:
    scenario = EpsteinScenario()

That should pass the rng=42 to the right place, right?

citizen.move_to(cell)
if klass is not None:
agent = klass(self) # Either Citizen or Cop
agent.move_to(cell)
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.

this change higlights how Scenario can really clean up stuff.

Copy link
Copy Markdown
Member Author

@EwoutH EwoutH Jan 18, 2026

Choose a reason for hiding this comment

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

Yes, I was very happy when I realized this could be simplified!

Copy link
Copy Markdown
Member

@quaquel quaquel left a comment

Choose a reason for hiding this comment

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

A few minor suggestions and clarification questions, but once addressed feel free to merge.

@quaquel
Copy link
Copy Markdown
Member

quaquel commented Jan 23, 2026

It might be good to test this against #3178 to see if it works with solara. If I have time today, I'll try that and report back here.

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 23, 2026

Awesome, thanks!

EwoutH and others added 3 commits January 23, 2026 14:34
… `Scenario` class

Replaces individual model and agent parameters with a Scenario object, centralizing configuration for agent vision, movement, legitimacy, jail terms, and other settings. Updates agent and model initialization to reference scenario attributes.
Replaces the default_scenario instance with a typed EpsteinScenario subclass containing default parameters. The model now creates a default EpsteinScenario if none is provided, improving type safety and clarity of scenario configuration.
@EwoutH EwoutH force-pushed the agent_scenario_property branch from ac74c67 to ded7a68 Compare January 23, 2026 13:34
@@ -1,10 +1,30 @@
import mesa
from mesa.discrete_space import OrthogonalMooreGrid, OrthogonalVonNeumannGrid #
from mesa.discrete_space import OrthogonalMooreGrid, OrthogonalVonNeumannGrid
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.

Suggested change
from mesa.discrete_space import OrthogonalMooreGrid, OrthogonalVonNeumannGrid
from mesa.discrete_space import OrthogonalMooreGrid, OrthogonalVonNeumannGrid
from typing import Literal



# Define a typed scenario subclass with defaults
class EpsteinScenario(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.

this should be used correctly in app.py

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Feb 11, 2026

It might be good to test this against #3178 to see if it works with solara. If I have time today, I'll try that and report back here.

Considering this and the interaction with #3178, what would your preference be that I did with this PR?

@quaquel
Copy link
Copy Markdown
Member

quaquel commented Feb 11, 2026

I'll try to take a look and see what's going on with #3178 or, otherwise, open a new PR myself to fix it.

@quaquel
Copy link
Copy Markdown
Member

quaquel commented Feb 14, 2026

I can also confirm that this example now runs locally.

@quaquel quaquel merged commit f95909e into mesa:main Feb 14, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

example Changes the examples or adds to them.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants