or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

batch-running.mdcore.mddata-collection.mdexperimental.mdindex.mdspatial.md

experimental.mddocs/

0

# Experimental Features

1

2

Mesa's experimental package contains cutting-edge features under active development. These modules provide advanced capabilities including discrete event simulation, reactive programming, enhanced continuous spaces, and modern visualization tools.

3

4

**⚠️ Warning:** Experimental features may have API changes between releases. Use in production with caution and pin specific Mesa versions.

5

6

## Imports

7

8

```python { .api }

9

# Main experimental package

10

from mesa import experimental

11

12

# Submodules

13

from mesa.experimental import devs

14

from mesa.experimental import mesa_signals

15

from mesa.experimental import meta_agents

16

from mesa.experimental import continuous_space

17

18

# Visualization (experimental)

19

from mesa.visualization import SolaraViz, JupyterViz

20

```

21

22

## Discrete Event Simulation (DEVS)

23

24

The DEVS (Discrete Event Simulation) framework enables event-driven modeling where system state changes occur at specific time points rather than fixed time steps.

25

26

### Core DEVS Classes

27

28

```python { .api }

29

from mesa.experimental.devs import DEVSModel, DEVSAgent, Event, EventScheduler

30

31

class Event:

32

"""

33

Discrete event with timing and payload information.

34

35

Events represent state changes that occur at specific simulation times,

36

enabling more efficient simulation of systems with sparse activity.

37

"""

38

39

def __init__(self, time: float, event_type: str, data: dict = None):

40

"""

41

Initialize an event.

42

43

Parameters:

44

time: When the event should occur (simulation time)

45

event_type: Type identifier for the event

46

data: Additional event payload data

47

"""

48

...

49

50

@property

51

def time(self) -> float:

52

"""Event occurrence time."""

53

...

54

55

@property

56

def event_type(self) -> str:

57

"""Event type identifier."""

58

...

59

60

@property

61

def data(self) -> dict:

62

"""Event data payload."""

63

...

64

65

class EventScheduler:

66

"""

67

Priority queue-based event scheduler for DEVS simulation.

68

69

Manages the event queue and provides efficient scheduling

70

and execution of discrete events.

71

"""

72

73

def __init__(self):

74

"""Initialize empty event scheduler."""

75

...

76

77

def schedule_event(self, event: Event):

78

"""

79

Schedule an event for future execution.

80

81

Parameters:

82

event: Event to schedule

83

"""

84

...

85

86

def schedule_in(self, delay: float, event_type: str, data: dict = None) -> Event:

87

"""

88

Schedule an event after a delay from current time.

89

90

Parameters:

91

delay: Time delay from current simulation time

92

event_type: Type of event to schedule

93

data: Event data payload

94

95

Returns:

96

Scheduled Event object

97

"""

98

...

99

100

def get_next_event(self) -> Event | None:

101

"""

102

Get and remove the next scheduled event.

103

104

Returns:

105

Next event to process, or None if queue is empty

106

"""

107

...

108

109

def peek_next_event(self) -> Event | None:

110

"""

111

Peek at next event without removing it.

112

113

Returns:

114

Next event or None if queue is empty

115

"""

116

...

117

118

@property

119

def current_time(self) -> float:

120

"""Current simulation time."""

121

...

122

123

class DEVSAgent(Agent):

124

"""

125

Agent class for discrete event simulation models.

126

127

Extends basic Agent with event handling capabilities and

128

lifecycle management for event-driven simulation.

129

"""

130

131

def __init__(self, model: 'DEVSModel'):

132

"""

133

Initialize DEVS agent.

134

135

Parameters:

136

model: The DEVS model containing this agent

137

"""

138

super().__init__(model)

139

...

140

141

def handle_event(self, event: Event):

142

"""

143

Process an incoming event.

144

145

This method should be overridden by subclasses to define

146

agent-specific event handling behavior.

147

148

Parameters:

149

event: Event to process

150

"""

151

...

152

153

def schedule_event(self, delay: float, event_type: str, data: dict = None) -> Event:

154

"""

155

Schedule an event for this agent.

156

157

Parameters:

158

delay: Time delay from current time

159

event_type: Type of event to schedule

160

data: Event data payload

161

162

Returns:

163

Scheduled Event object

164

"""

165

return self.model.scheduler.schedule_in(delay, event_type, data)

166

167

def on_activate(self):

168

"""Called when agent is activated in the simulation."""

169

...

170

171

def on_deactivate(self):

172

"""Called when agent is deactivated/removed."""

173

...

174

175

class DEVSModel(Model):

176

"""

177

Model class for discrete event simulation.

178

179

Manages event-driven simulation with precise timing and

180

efficient execution of sparse events.

181

"""

182

183

def __init__(self, **kwargs):

184

"""

185

Initialize DEVS model.

186

187

Parameters:

188

**kwargs: Model configuration parameters

189

"""

190

super().__init__(**kwargs)

191

self.scheduler = EventScheduler()

192

...

193

194

def step(self):

195

"""

196

Execute one simulation step (process next event).

197

198

In DEVS, a step processes the next scheduled event

199

rather than advancing by a fixed time increment.

200

"""

201

event = self.scheduler.get_next_event()

202

if event:

203

self._process_event(event)

204

else:

205

self.running = False # No more events

206

207

def _process_event(self, event: Event):

208

"""

209

Process a discrete event.

210

211

Parameters:

212

event: Event to process

213

"""

214

# Update simulation time

215

self.scheduler.current_time = event.time

216

217

# Handle model-level events

218

if hasattr(self, f'handle_{event.event_type}'):

219

handler = getattr(self, f'handle_{event.event_type}')

220

handler(event)

221

222

def run_until(self, end_time: float):

223

"""

224

Run simulation until specified time.

225

226

Parameters:

227

end_time: Simulation time to run until

228

"""

229

while (self.running and

230

self.scheduler.peek_next_event() and

231

self.scheduler.peek_next_event().time <= end_time):

232

self.step()

233

234

@property

235

def current_time(self) -> float:

236

"""Current simulation time."""

237

return self.scheduler.current_time

238

```

239

240

### DEVS Usage Example

241

242

```python { .api }

243

from mesa.experimental.devs import DEVSModel, DEVSAgent, Event

244

import random

245

246

class Customer(DEVSAgent):

247

"""Customer agent that generates service requests."""

248

249

def __init__(self, model, arrival_rate=1.0):

250

super().__init__(model)

251

self.arrival_rate = arrival_rate

252

self.service_time = 0

253

254

def on_activate(self):

255

"""Schedule first arrival."""

256

inter_arrival_time = random.expovariate(self.arrival_rate)

257

self.schedule_event(inter_arrival_time, "arrival", {"customer_id": self.unique_id})

258

259

def handle_event(self, event):

260

if event.event_type == "arrival":

261

# Process service request

262

self.model.handle_customer_arrival(self)

263

264

# Schedule next arrival

265

inter_arrival_time = random.expovariate(self.arrival_rate)

266

self.schedule_event(inter_arrival_time, "arrival", {"customer_id": self.unique_id})

267

268

elif event.event_type == "service_complete":

269

# Customer leaves system

270

self.service_time = event.data.get("service_duration", 0)

271

self.model.log_service_completion(self)

272

273

class Server(DEVSAgent):

274

"""Server agent that processes customer requests."""

275

276

def __init__(self, model, service_rate=2.0):

277

super().__init__(model)

278

self.service_rate = service_rate

279

self.busy = False

280

self.queue = []

281

self.customers_served = 0

282

283

def handle_event(self, event):

284

if event.event_type == "service_request":

285

customer = event.data["customer"]

286

287

if not self.busy:

288

# Start service immediately

289

self._start_service(customer)

290

else:

291

# Add to queue

292

self.queue.append(customer)

293

294

elif event.event_type == "service_complete":

295

self.busy = False

296

self.customers_served += 1

297

298

# Notify customer

299

customer = event.data["customer"]

300

customer_event = Event(

301

self.model.current_time,

302

"service_complete",

303

{"service_duration": event.data["service_duration"]}

304

)

305

customer.handle_event(customer_event)

306

307

# Serve next customer if any

308

if self.queue:

309

next_customer = self.queue.pop(0)

310

self._start_service(next_customer)

311

312

def _start_service(self, customer):

313

"""Start serving a customer."""

314

self.busy = True

315

service_duration = random.expovariate(self.service_rate)

316

317

self.schedule_event(service_duration, "service_complete", {

318

"customer": customer,

319

"service_duration": service_duration

320

})

321

322

class QueuingModel(DEVSModel):

323

"""Queueing system simulation using DEVS."""

324

325

def __init__(self, n_customers=5, n_servers=2, arrival_rate=1.0, service_rate=2.0):

326

super().__init__()

327

328

self.arrival_rate = arrival_rate

329

self.service_rate = service_rate

330

331

# Create servers

332

self.servers = [Server(self, service_rate) for _ in range(n_servers)]

333

for server in self.servers:

334

self.register_agent(server)

335

336

# Create customers

337

for i in range(n_customers):

338

customer = Customer(self, arrival_rate)

339

self.register_agent(customer)

340

customer.on_activate()

341

342

# Statistics tracking

343

self.total_arrivals = 0

344

self.total_completions = 0

345

self.service_times = []

346

347

self.running = True

348

349

def handle_customer_arrival(self, customer):

350

"""Handle customer arrival - assign to server."""

351

self.total_arrivals += 1

352

353

# Find server with shortest queue

354

best_server = min(self.servers, key=lambda s: len(s.queue) if s.busy else 0)

355

356

# Send service request to server

357

request_event = Event(

358

self.current_time,

359

"service_request",

360

{"customer": customer}

361

)

362

best_server.handle_event(request_event)

363

364

def log_service_completion(self, customer):

365

"""Log completed service."""

366

self.total_completions += 1

367

self.service_times.append(customer.service_time)

368

369

def get_statistics(self):

370

"""Get simulation statistics."""

371

return {

372

"total_arrivals": self.total_arrivals,

373

"total_completions": self.total_completions,

374

"avg_service_time": sum(self.service_times) / len(self.service_times) if self.service_times else 0,

375

"server_utilization": [s.customers_served / self.current_time * self.service_rate

376

for s in self.servers]

377

}

378

379

# Run DEVS simulation

380

model = QueuingModel(n_customers=10, n_servers=3, arrival_rate=0.8, service_rate=1.5)

381

model.run_until(100.0) # Run for 100 time units

382

383

stats = model.get_statistics()

384

print(f"Simulation completed at time {model.current_time}")

385

print(f"Arrivals: {stats['total_arrivals']}, Completions: {stats['total_completions']}")

386

print(f"Average service time: {stats['avg_service_time']:.2f}")

387

print(f"Server utilizations: {[f'{u:.2%}' for u in stats['server_utilization']]}")

388

```

389

390

## Reactive Programming (mesa_signals)

391

392

The mesa_signals module provides reactive programming capabilities with observable collections and signal-based communication.

393

394

```python { .api }

395

from mesa.experimental.mesa_signals import ObservableAgentSet, Signal, Observer

396

397

class Signal:

398

"""

399

Signal system for reactive communication between model components.

400

401

Enables decoupled communication through publisher-subscriber patterns

402

and reactive updates based on state changes.

403

"""

404

405

def __init__(self, name: str):

406

"""

407

Initialize signal.

408

409

Parameters:

410

name: Signal identifier

411

"""

412

...

413

414

def connect(self, handler: callable, sender=None):

415

"""

416

Connect a handler to this signal.

417

418

Parameters:

419

handler: Function to call when signal is emitted

420

sender: Optional specific sender to listen for

421

"""

422

...

423

424

def disconnect(self, handler: callable, sender=None):

425

"""

426

Disconnect a handler from this signal.

427

428

Parameters:

429

handler: Handler function to disconnect

430

sender: Optional specific sender

431

"""

432

...

433

434

def emit(self, sender=None, **kwargs):

435

"""

436

Emit the signal to all connected handlers.

437

438

Parameters:

439

sender: Object emitting the signal

440

**kwargs: Signal data payload

441

"""

442

...

443

444

class ObservableAgentSet(AgentSet):

445

"""

446

AgentSet with reactive capabilities and change notifications.

447

448

Extends standard AgentSet with signal emission for collection

449

changes, enabling reactive responses to agent additions/removals.

450

"""

451

452

def __init__(self, agents=None, random=None):

453

"""

454

Initialize observable agent set.

455

456

Parameters:

457

agents: Initial agents to include

458

random: Random number generator

459

"""

460

super().__init__(agents, random)

461

462

# Signals for collection changes

463

self.agent_added = Signal("agent_added")

464

self.agent_removed = Signal("agent_removed")

465

self.collection_changed = Signal("collection_changed")

466

467

def add(self, agent):

468

"""Add agent and emit signals."""

469

super().add(agent)

470

self.agent_added.emit(sender=self, agent=agent)

471

self.collection_changed.emit(sender=self, change_type="add", agent=agent)

472

473

def remove(self, agent):

474

"""Remove agent and emit signals."""

475

super().remove(agent)

476

self.agent_removed.emit(sender=self, agent=agent)

477

self.collection_changed.emit(sender=self, change_type="remove", agent=agent)

478

479

class Observer:

480

"""

481

Base class for reactive observers that respond to signals.

482

483

Provides infrastructure for objects that need to react to

484

changes in model state or agent collections.

485

"""

486

487

def __init__(self, name: str = None):

488

"""

489

Initialize observer.

490

491

Parameters:

492

name: Optional observer identifier

493

"""

494

self.name = name

495

self._connections = []

496

497

def observe(self, signal: Signal, handler: callable, sender=None):

498

"""

499

Start observing a signal.

500

501

Parameters:

502

signal: Signal to observe

503

handler: Method to call when signal emits

504

sender: Optional specific sender to observe

505

"""

506

signal.connect(handler, sender)

507

self._connections.append((signal, handler, sender))

508

509

def stop_observing(self, signal: Signal = None):

510

"""

511

Stop observing signals.

512

513

Parameters:

514

signal: Specific signal to stop observing (None for all)

515

"""

516

if signal:

517

connections_to_remove = [c for c in self._connections if c[0] == signal]

518

for conn in connections_to_remove:

519

signal.disconnect(conn[1], conn[2])

520

self._connections.remove(conn)

521

else:

522

# Disconnect all

523

for signal, handler, sender in self._connections:

524

signal.disconnect(handler, sender)

525

self._connections.clear()

526

```

527

528

### Reactive Programming Example

529

530

```python { .api }

531

from mesa import Agent, Model

532

from mesa.experimental.mesa_signals import ObservableAgentSet, Signal, Observer

533

534

class ReactiveAgent(Agent):

535

"""Agent that emits signals for state changes."""

536

537

def __init__(self, model, wealth=100):

538

super().__init__(model)

539

self.wealth = wealth

540

self._previous_wealth = wealth

541

542

# Signals for state changes

543

self.wealth_changed = Signal("wealth_changed")

544

self.became_wealthy = Signal("became_wealthy")

545

self.became_poor = Signal("became_poor")

546

547

def step(self):

548

# Simple wealth dynamics

549

change = self.random.randint(-10, 15)

550

self.wealth += change

551

self.wealth = max(0, self.wealth)

552

553

# Emit wealth change signal

554

if self.wealth != self._previous_wealth:

555

self.wealth_changed.emit(

556

sender=self,

557

old_wealth=self._previous_wealth,

558

new_wealth=self.wealth,

559

change=change

560

)

561

562

# Emit status change signals

563

if self._previous_wealth < 200 and self.wealth >= 200:

564

self.became_wealthy.emit(sender=self, wealth=self.wealth)

565

elif self._previous_wealth >= 50 and self.wealth < 50:

566

self.became_poor.emit(sender=self, wealth=self.wealth)

567

568

self._previous_wealth = self.wealth

569

570

class WealthTracker(Observer):

571

"""Observer that tracks wealth statistics."""

572

573

def __init__(self):

574

super().__init__("WealthTracker")

575

self.wealth_changes = []

576

self.wealthy_agents = set()

577

self.poor_agents = set()

578

579

def on_wealth_change(self, sender, old_wealth, new_wealth, change, **kwargs):

580

"""Handle wealth change events."""

581

self.wealth_changes.append({

582

'agent_id': sender.unique_id,

583

'old_wealth': old_wealth,

584

'new_wealth': new_wealth,

585

'change': change,

586

'timestamp': sender.model.steps

587

})

588

589

def on_became_wealthy(self, sender, wealth, **kwargs):

590

"""Handle agents becoming wealthy."""

591

self.wealthy_agents.add(sender.unique_id)

592

print(f"Agent {sender.unique_id} became wealthy with ${wealth}")

593

594

def on_became_poor(self, sender, wealth, **kwargs):

595

"""Handle agents becoming poor."""

596

self.poor_agents.add(sender.unique_id)

597

print(f"Agent {sender.unique_id} became poor with ${wealth}")

598

599

def get_statistics(self):

600

"""Get tracked statistics."""

601

return {

602

'total_changes': len(self.wealth_changes),

603

'wealthy_count': len(self.wealthy_agents),

604

'poor_count': len(self.poor_agents),

605

'avg_change': sum(c['change'] for c in self.wealth_changes) / len(self.wealth_changes)

606

if self.wealth_changes else 0

607

}

608

609

class ReactiveModel(Model):

610

"""Model using reactive programming patterns."""

611

612

def __init__(self, n_agents=50):

613

super().__init__()

614

615

# Use observable agent set

616

self._agents = ObservableAgentSet()

617

618

# Create wealth tracker

619

self.wealth_tracker = WealthTracker()

620

621

# Market signals

622

self.market_crash = Signal("market_crash")

623

self.market_boom = Signal("market_boom")

624

625

# Connect market event handlers

626

self.market_crash.connect(self.handle_market_crash)

627

self.market_boom.connect(self.handle_market_boom)

628

629

# Create agents and observe their signals

630

for i in range(n_agents):

631

agent = ReactiveAgent(self, wealth=self.random.randint(50, 200))

632

633

# Connect agent signals to tracker

634

self.wealth_tracker.observe(agent.wealth_changed, self.wealth_tracker.on_wealth_change)

635

self.wealth_tracker.observe(agent.became_wealthy, self.wealth_tracker.on_became_wealthy)

636

self.wealth_tracker.observe(agent.became_poor, self.wealth_tracker.on_became_poor)

637

638

# Observe agent collection changes

639

self._agents.agent_added.connect(self.on_agent_added)

640

self._agents.agent_removed.connect(self.on_agent_removed)

641

642

self.running = True

643

644

@property

645

def agents(self):

646

"""Override agents property to return observable set."""

647

return self._agents

648

649

def register_agent(self, agent):

650

"""Override to use observable agent set."""

651

self._agents.add(agent)

652

653

def deregister_agent(self, agent):

654

"""Override to use observable agent set."""

655

self._agents.discard(agent)

656

657

def on_agent_added(self, sender, agent, **kwargs):

658

"""Handle new agents being added."""

659

print(f"New agent {agent.unique_id} joined the simulation")

660

661

def on_agent_removed(self, sender, agent, **kwargs):

662

"""Handle agents being removed."""

663

print(f"Agent {agent.unique_id} left the simulation")

664

665

def handle_market_crash(self, sender, severity=0.5, **kwargs):

666

"""Handle market crash events."""

667

print(f"Market crash! Severity: {severity}")

668

for agent in self.agents:

669

loss = int(agent.wealth * severity)

670

agent.wealth = max(0, agent.wealth - loss)

671

672

def handle_market_boom(self, sender, multiplier=1.5, **kwargs):

673

"""Handle market boom events."""

674

print(f"Market boom! Multiplier: {multiplier}")

675

for agent in self.agents:

676

agent.wealth = int(agent.wealth * multiplier)

677

678

def step(self):

679

self.agents.shuffle_do("step")

680

681

# Random market events

682

if self.random.random() < 0.05: # 5% chance

683

if self.random.random() < 0.3: # 30% of events are crashes

684

self.market_crash.emit(sender=self, severity=self.random.uniform(0.2, 0.8))

685

else: # 70% are booms

686

self.market_boom.emit(sender=self, multiplier=self.random.uniform(1.1, 1.8))

687

688

# Run reactive simulation

689

model = ReactiveModel(n_agents=20)

690

for i in range(50):

691

model.step()

692

693

# Get wealth tracking statistics

694

stats = model.wealth_tracker.get_statistics()

695

print(f"\nWealth tracking results:")

696

print(f"Total wealth changes: {stats['total_changes']}")

697

print(f"Agents who became wealthy: {stats['wealthy_count']}")

698

print(f"Agents who became poor: {stats['poor_count']}")

699

print(f"Average wealth change: ${stats['avg_change']:.2f}")

700

```

701

702

## Enhanced Continuous Space

703

704

The experimental continuous space module provides improved continuous spatial modeling with enhanced performance and features.

705

706

```python { .api }

707

from mesa.experimental.continuous_space import ContinuousSpace3D, SpatialIndex

708

709

class ContinuousSpace3D:

710

"""

711

Three-dimensional continuous space with enhanced spatial queries.

712

713

Extends 2D continuous space to support 3D positioning and

714

spatial relationships with optimized neighbor searching.

715

"""

716

717

def __init__(self, x_max: float, y_max: float, z_max: float,

718

torus: bool = False, x_min: float = 0,

719

y_min: float = 0, z_min: float = 0):

720

"""

721

Initialize 3D continuous space.

722

723

Parameters:

724

x_max: Maximum x coordinate

725

y_max: Maximum y coordinate

726

z_max: Maximum z coordinate

727

torus: Whether space wraps around at boundaries

728

x_min: Minimum x coordinate

729

y_min: Minimum y coordinate

730

z_min: Minimum z coordinate

731

"""

732

...

733

734

def place_agent(self, agent, pos: tuple[float, float, float]):

735

"""

736

Place agent at 3D coordinates.

737

738

Parameters:

739

agent: Agent to place

740

pos: (x, y, z) coordinate tuple

741

"""

742

...

743

744

def move_agent(self, agent, pos: tuple[float, float, float]):

745

"""Move agent to new 3D coordinates."""

746

...

747

748

def get_neighbors(self, pos: tuple[float, float, float], radius: float,

749

include_center: bool = False) -> list[Agent]:

750

"""

751

Get agents within 3D radius of position.

752

753

Parameters:

754

pos: Center position (x, y, z)

755

radius: Search radius

756

include_center: Whether to include agents at exact center

757

"""

758

...

759

760

def get_distance_3d(self, pos_1: tuple[float, float, float],

761

pos_2: tuple[float, float, float]) -> float:

762

"""Get 3D Euclidean distance between positions."""

763

...

764

765

class SpatialIndex:

766

"""

767

Spatial index for efficient neighbor queries in continuous spaces.

768

769

Provides optimized spatial data structures for fast proximity

770

searches in large continuous spaces.

771

"""

772

773

def __init__(self, bounds: tuple[float, float, float, float]):

774

"""

775

Initialize spatial index.

776

777

Parameters:

778

bounds: (x_min, y_min, x_max, y_max) bounding rectangle

779

"""

780

...

781

782

def insert(self, agent, pos: tuple[float, float]):

783

"""Insert agent at position into spatial index."""

784

...

785

786

def remove(self, agent):

787

"""Remove agent from spatial index."""

788

...

789

790

def query_radius(self, pos: tuple[float, float], radius: float) -> list[Agent]:

791

"""Query agents within radius efficiently."""

792

...

793

794

def query_rectangle(self, bounds: tuple[float, float, float, float]) -> list[Agent]:

795

"""Query agents within rectangular bounds."""

796

...

797

```

798

799

## Meta Agents

800

801

Meta-agent functionality for hierarchical and composite agent systems.

802

803

```python { .api }

804

from mesa.experimental.meta_agents import MetaAgent, AgentGroup, HierarchicalAgent

805

806

class MetaAgent(Agent):

807

"""

808

Agent that can contain and manage other agents.

809

810

Enables hierarchical agent structures and composite behaviors

811

for complex multi-level agent-based models.

812

"""

813

814

def __init__(self, model, sub_agents=None):

815

"""

816

Initialize meta-agent.

817

818

Parameters:

819

model: The model instance

820

sub_agents: Initial sub-agents to contain

821

"""

822

super().__init__(model)

823

self.sub_agents = AgentSet(sub_agents or [])

824

...

825

826

def add_sub_agent(self, agent):

827

"""Add a sub-agent to this meta-agent."""

828

...

829

830

def remove_sub_agent(self, agent):

831

"""Remove a sub-agent from this meta-agent."""

832

...

833

834

def step(self):

835

"""Execute meta-agent behavior and coordinate sub-agents."""

836

# Meta-agent logic

837

self._execute_meta_behavior()

838

839

# Coordinate sub-agent actions

840

self.sub_agents.shuffle_do("step")

841

842

def _execute_meta_behavior(self):

843

"""Execute meta-level behavior - override in subclasses."""

844

pass

845

846

class AgentGroup(MetaAgent):

847

"""

848

Group of agents that act collectively.

849

850

Provides coordination mechanisms for groups of agents

851

to achieve collective goals and behaviors.

852

"""

853

854

def __init__(self, model, agents=None, coordination_strategy="consensus"):

855

"""

856

Initialize agent group.

857

858

Parameters:

859

model: Model instance

860

agents: Agents to include in group

861

coordination_strategy: How to coordinate group decisions

862

"""

863

super().__init__(model, agents)

864

self.coordination_strategy = coordination_strategy

865

self.group_goals = []

866

...

867

868

def add_group_goal(self, goal):

869

"""Add a collective goal for the group."""

870

...

871

872

def coordinate_action(self, action_type: str, **kwargs):

873

"""Coordinate a collective action among group members."""

874

if self.coordination_strategy == "consensus":

875

return self._consensus_coordination(action_type, **kwargs)

876

elif self.coordination_strategy == "leader":

877

return self._leader_coordination(action_type, **kwargs)

878

# Additional coordination strategies...

879

880

def _consensus_coordination(self, action_type: str, **kwargs):

881

"""Coordinate through consensus among group members."""

882

...

883

884

def _leader_coordination(self, action_type: str, **kwargs):

885

"""Coordinate through designated leader."""

886

...

887

```

888

889

## Visualization (Experimental)

890

891

Modern browser-based visualization using Solara.

892

893

```python { .api }

894

from mesa.visualization import SolaraViz, JupyterViz, make_space_altair

895

896

class SolaraViz:

897

"""

898

Browser-based visualization using Solara framework.

899

900

Provides interactive, modern web-based visualization for Mesa models

901

with real-time updates and user controls.

902

"""

903

904

def __init__(self,

905

model_cls,

906

model_params=None,

907

measures=None,

908

name="Mesa Model",

909

space_drawer=None,

910

agent_portrayal=None):

911

"""

912

Initialize Solara visualization.

913

914

Parameters:

915

model_cls: Mesa model class to visualize

916

model_params: Dictionary of model parameters with UI controls

917

measures: List of measures to plot over time

918

name: Display name for the model

919

space_drawer: Function to draw spatial elements

920

agent_portrayal: Function to define agent appearance

921

"""

922

...

923

924

def launch(self, port=8521):

925

"""

926

Launch the visualization server.

927

928

Parameters:

929

port: Port number for the web server

930

"""

931

...

932

933

class JupyterViz:

934

"""

935

Jupyter notebook visualization component.

936

937

Provides inline visualization widgets for Jupyter notebooks

938

with interactive controls and real-time model execution.

939

"""

940

941

def __init__(self,

942

model_cls,

943

model_params=None,

944

measures=None,

945

space_drawer=None,

946

agent_portrayal=None):

947

"""

948

Initialize Jupyter visualization.

949

950

Parameters:

951

model_cls: Mesa model class

952

model_params: Parameter controls

953

measures: Measures to display

954

space_drawer: Spatial visualization function

955

agent_portrayal: Agent appearance function

956

"""

957

...

958

959

def show(self):

960

"""Display the visualization widget in Jupyter."""

961

...

962

963

def make_space_altair(space_drawer):

964

"""

965

Create Altair-based space visualization.

966

967

Parameters:

968

space_drawer: Function that returns visualization data

969

970

Returns:

971

Altair chart specification for spatial visualization

972

"""

973

...

974

```

975

976

### Visualization Example

977

978

```python { .api }

979

from mesa import Agent, Model

980

from mesa.space import MultiGrid

981

from mesa.datacollection import DataCollector

982

from mesa.visualization import SolaraViz

983

import altair as alt

984

985

class VisualizationAgent(Agent):

986

def __init__(self, model, agent_type="normal"):

987

super().__init__(model)

988

self.agent_type = agent_type

989

self.energy = 100

990

self.size = self.random.uniform(0.5, 1.5)

991

992

def step(self):

993

# Simple movement

994

neighbors = self.model.grid.get_neighborhood(self.pos, moore=True)

995

new_pos = self.random.choice(neighbors)

996

self.model.grid.move_agent(self, new_pos)

997

998

# Energy dynamics

999

self.energy += self.random.randint(-5, 10)

1000

self.energy = max(0, min(200, self.energy))

1001

1002

class VisualizationModel(Model):

1003

def __init__(self, n_agents=50, width=20, height=20, agent_types=None):

1004

super().__init__()

1005

1006

self.grid = MultiGrid(width, height, torus=True)

1007

1008

self.datacollector = DataCollector(

1009

model_reporters={

1010

"Total Agents": lambda m: len(m.agents),

1011

"Average Energy": lambda m: sum(a.energy for a in m.agents) / len(m.agents),

1012

"High Energy Agents": lambda m: len([a for a in m.agents if a.energy > 150])

1013

}

1014

)

1015

1016

agent_types = agent_types or ["normal", "special", "rare"]

1017

1018

# Create agents

1019

for i in range(n_agents):

1020

agent_type = self.random.choice(agent_types)

1021

agent = VisualizationAgent(self, agent_type=agent_type)

1022

1023

# Place randomly

1024

x = self.random.randrange(width)

1025

y = self.random.randrange(height)

1026

self.grid.place_agent(agent, (x, y))

1027

1028

self.running = True

1029

1030

def step(self):

1031

self.datacollector.collect(self)

1032

self.agents.shuffle_do("step")

1033

1034

# Agent portrayal for visualization

1035

def agent_portrayal(agent):

1036

"""Define how agents appear in visualization."""

1037

size = agent.size * 20 # Scale for visibility

1038

1039

# Color by type and energy

1040

if agent.agent_type == "special":

1041

color = "blue"

1042

elif agent.agent_type == "rare":

1043

color = "red"

1044

else:

1045

color = "green"

1046

1047

# Adjust opacity by energy

1048

opacity = agent.energy / 200.0

1049

1050

return {

1051

"color": color,

1052

"size": size,

1053

"opacity": opacity,

1054

"shape": "circle"

1055

}

1056

1057

# Model parameters for UI controls

1058

model_params = {

1059

"n_agents": {

1060

"type": "SliderInt",

1061

"value": 50,

1062

"label": "Number of Agents",

1063

"min": 10,

1064

"max": 200,

1065

"step": 10

1066

},

1067

"width": {

1068

"type": "SliderInt",

1069

"value": 20,

1070

"label": "Grid Width",

1071

"min": 10,

1072

"max": 50

1073

},

1074

"height": {

1075

"type": "SliderInt",

1076

"value": 20,

1077

"label": "Grid Height",

1078

"min": 10,

1079

"max": 50

1080

}

1081

}

1082

1083

# Measures to plot

1084

measures = ["Total Agents", "Average Energy", "High Energy Agents"]

1085

1086

# Create and launch visualization

1087

viz = SolaraViz(

1088

model_cls=VisualizationModel,

1089

model_params=model_params,

1090

measures=measures,

1091

name="Agent Energy Dynamics",

1092

agent_portrayal=agent_portrayal

1093

)

1094

1095

# Launch in browser (uncomment to run)

1096

# viz.launch(port=8521)

1097

1098

# Or for Jupyter notebooks

1099

jupyter_viz = JupyterViz(

1100

model_cls=VisualizationModel,

1101

model_params=model_params,

1102

measures=measures,

1103

agent_portrayal=agent_portrayal

1104

)

1105

1106

# Display in notebook (uncomment to run)

1107

# jupyter_viz.show()

1108

```

1109

1110

## Migration and Compatibility

1111

1112

### Using Experimental Features Safely

1113

1114

```python { .api }

1115

# Pin Mesa version when using experimental features

1116

# requirements.txt:

1117

# Mesa==3.2.0 # Specific version for API stability

1118

1119

# Graceful fallbacks for missing experimental features

1120

try:

1121

from mesa.experimental.devs import DEVSModel

1122

HAS_DEVS = True

1123

except ImportError:

1124

HAS_DEVS = False

1125

DEVSModel = None

1126

1127

class AdaptiveModel(Model):

1128

"""Model that adapts to available experimental features."""

1129

1130

def __init__(self, use_devs=True, **kwargs):

1131

if use_devs and HAS_DEVS:

1132

# Use DEVS if available

1133

self._use_devs_features()

1134

else:

1135

# Fall back to standard Mesa

1136

super().__init__(**kwargs)

1137

1138

def _use_devs_features(self):

1139

"""Initialize DEVS-specific features."""

1140

# DEVS initialization code

1141

pass

1142

```

1143

1144

## Best Practices for Experimental Features

1145

1146

1. **Version Pinning**: Always pin specific Mesa versions when using experimental features

1147

2. **Fallback Strategies**: Implement graceful degradation when experimental features aren't available

1148

3. **Documentation**: Document which experimental features your model uses

1149

4. **Testing**: Thoroughly test models using experimental features across Mesa versions

1150

5. **Community Engagement**: Provide feedback on experimental features to help guide development

1151

1152

The experimental package represents the cutting edge of Mesa development. While these features provide powerful new capabilities, use them thoughtfully in production environments and stay engaged with the Mesa community for updates and best practices.