Skip to content

Conversation

@Nithurshen
Copy link
Contributor

@Nithurshen Nithurshen commented Dec 22, 2025

Summary

Fixes #2961

This PR introduces the AdaptiveVoronoiSpace class, a dynamic spatial component for Mesa that reconfigures its Voronoi regions based on real-time agent density and distribution. By leveraging K-Means clustering, the space allows regional boundaries to emerge organically from agent positions rather than relying on a static grid.

Motive

Traditional spatial models often use fixed environments. However, many social and biological phenomena, such as urban growth, cultural regionalization, and economic market territories involve boundaries that shift as agents move. This feature provides a native way to study these emergent spatial patterns, enabling agents to sense and react to local regions that are themselves defined by the collective distribution of agents.

Implementation

The feature is built as a subclass of ContinuousSpace to ensure high performance and compatibility with existing Mesa utilities.

  1. Inherits from ContinuousSpace to reuse optimized NumPy position caches (_agent_points).
  2. Uses sklearn.cluster.KMeans to find centroids based on current agent coordinates.
  3. Implements scipy.spatial.cKDTree for O(logN) region membership lookups, avoiding the need for complex polygon geometry calculations.
  4. Features a rebuild_voronoi() method that allows developers to control the frequency of spatial reconfiguration to balance accuracy and performance.

Usage Examples

Developers can initialize the space by specifying the desired number of dynamic clusters.

import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import voronoi_plot_2d, Voronoi
from mesa.space_adaptive import AdaptiveVoronoiSpace
from mesa.agent import Agent

class MockAgent(Agent):
    def __init__(self, unique_id, model, pos):
        self.unique_id = unique_id
        self.model = model
        self.pos = np.array(pos)

def visualize():
    WIDTH, HEIGHT = 100, 100
    N_CLUSTERS = 5
    space = AdaptiveVoronoiSpace(WIDTH, HEIGHT, torus=False, n_clusters=N_CLUSTERS)
    
    print("Generating agents...")
    centers = [(20, 20), (80, 80), (20, 80), (80, 20), (50, 50)]
    agent_id = 0
    
    for center in centers:
        blob = np.random.normal(loc=center, scale=8, size=(20, 2))
        blob = np.clip(blob, 0, WIDTH)
        
        for pos in blob:
            a = MockAgent(agent_id, None, pos)
            space.place_agent(a, pos)
            agent_id += 1

    print("Rebuilding Voronoi Tessellation...")
    space.rebuild_voronoi()
    
    print("Generating plot...")
    fig, ax = plt.subplots(figsize=(10, 10))
    
    vor = Voronoi(space.centroids)
    voronoi_plot_2d(vor, ax=ax, show_vertices=False, line_colors='orange', line_width=2, line_alpha=0.6)
    
    labels = space.agent_labels
    points = space._agent_points
    ax.scatter(points[:, 0], points[:, 1], c=labels, cmap='tab10', s=30, alpha=0.8, label='Agents')
    
    centroids = space.centroids
    ax.scatter(centroids[:, 0], centroids[:, 1], c='red', marker='X', s=200, label='Centroids', edgecolors='black')

    ax.set_xlim(0, WIDTH)
    ax.set_ylim(0, HEIGHT)
    ax.set_title(f"Adaptive Voronoi Space (Mesa)\n{agent_id} Agents -> {N_CLUSTERS} Dynamic Cells")
    ax.legend()
    
    filename = "adaptive_voronoi_demo.png"
    plt.savefig(filename)
    print(f"Saved visualization to {filename}")
    plt.show()

if __name__ == "__main__":
    visualize()

The provided visualization script uses scipy.spatial.voronoi_plot_2d to display the emergent cells.

The script generates 5 distinct blobs of agents and uses the adaptive logic to find the centroids and boundaries.

  • Blue/Orange Lines: Denote the Voronoi boundaries equidistant between centroids.
  • Red Crosses: Represent the calculated K-Means centroids.
  • Colored Agents: Demonstrate successful region assignment based on proximity to centroids.
adaptive_voronoi_demo

Additional Notes

  • Introduced scikit-learn and scipy. I have added these to a new adaptive optional dependency group in pyproject.toml to keep the core installation lightweight.
  • Included tests/test_space_adaptive.py with complete coverage of clustering logic, neighbor retrieval, and error handling for low agent counts.

I have added a full advanced example in mesa/examples/advanced/cultural_segregation/ that demonstrates a generative feedback loop:

  • Voronoi boundaries are updated every step based on where agents have migrated.

  • Agents analyze their Voronoi cell's average cultural opinion and decide whether to settle or move to find a more compatible region.

Screenshot 2025-12-24 at 16 00 46

@Nithurshen Nithurshen marked this pull request as draft December 22, 2025 14:10
@github-actions
Copy link

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small 🔵 -0.1% [-0.9%, +0.9%] 🔵 +0.8% [+0.6%, +0.9%]
BoltzmannWealth large 🔵 +1.2% [-2.0%, +4.7%] 🔵 -1.7% [-4.3%, +0.9%]
Schelling small 🔵 +0.2% [-0.9%, +1.4%] 🔵 +1.3% [+0.7%, +1.9%]
Schelling large 🔵 -0.3% [-1.4%, +1.6%] 🔵 -2.0% [-2.8%, -1.3%]
WolfSheep small 🔵 +0.5% [-0.3%, +1.1%] 🔵 +1.5% [+1.3%, +1.8%]
WolfSheep large 🔵 -0.1% [-3.6%, +2.0%] 🔵 +1.6% [+0.8%, +2.5%]
BoidFlockers small 🔵 +2.5% [+2.2%, +2.8%] 🔵 +1.7% [+1.5%, +1.9%]
BoidFlockers large 🔵 +1.9% [+1.6%, +2.3%] 🔵 +1.4% [+1.1%, +1.7%]

@EwoutH
Copy link
Member

EwoutH commented Dec 22, 2025

This is quite cool. Thanks, also for documenting it well and the visualization.

I think the big question now is, how does this fit in the bigger picture? Is this mainly a way to aggregate data? Or do agents also make decisions based on their cluster or cluster values? What are the use cases?

Building some example models with this might help with that.

@EwoutH EwoutH added feature Release notes label experimental Release notes label labels Dec 22, 2025
@Nithurshen
Copy link
Contributor Author

This is quite cool. Thanks, also for documenting it well and the visualization.

I think the big question now is, how does this fit in the bigger picture? Is this mainly a way to aggregate data? Or do agents also make decisions based on their cluster or cluster values? What are the use cases?

Building some example models with this might help with that.

@EwoutH, I see this being used for both.

  • Agents making decisions based on the aggregate properties of their local 'neighborhood' (Voronoi cell) which isn't confined to a fixed grid.
  • Modeling systems where boundaries are defined by the agents themselves (like market areas or foraging zones).

I'll work on a simple 'Cultural Segregation' example model to demonstrate how agents can interact with these emergent regions and use the cell's aggregate values to drive their behavior. I'll update the Draft PR once that's ready.

@Nithurshen
Copy link
Contributor Author

@EwoutH, kindly review it now.

@EwoutH
Copy link
Member

EwoutH commented Dec 24, 2025

Sorry I'm not going to be able to move this fast, since this is quite a large PR with a feature being added to our stable user API. That takes some consideration of complexity, proportionality, use cases.

We just took a sprint to get 3.4.0 out, so it might take a while. Sorry.

@Nithurshen
Copy link
Contributor Author

Sorry I'm not going to be able to move this fast, since this is quite a large PR with a feature being added to our stable user API. That takes some consideration of complexity, proportionality, use cases.

We just took a sprint to get 3.4.0 out, so it might take a while. Sorry.

I understand.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

experimental Release notes label feature Release notes label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support adaptive Voronoi space with agent-based clustering

2 participants