Repository: projectmesa/mesa

Issue:
Allow updating visualisation every n steps (instead of every step)
With many different plots the plotting of all the matplotlib components takes up significant time in the visualisations. An easy and simple way to speed this up would be only plotting the graphs every N steps. We could add a slider to the visualisation that defaults to 1 and allows updating the graphs less frequently for performance.

Hints from issue comments:
@HMNS19: Hello @EwoutH , I'd like to work on this issue. Can you assign this to me@EwoutH: Yes, let me know if you have any questions.@Sahil-Chhoker: @EwoutH I think this issue can be closed!

Solution patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 7209ceebcac..308e345a6a9 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,7 +4,7 @@ ci:
 repos:
 - repo: https://github.com/astral-sh/ruff-pre-commit
   # Ruff version.
-  rev: v0.8.1
+  rev: v0.8.6
   hooks:
     # Run the linter.
     - id: ruff
@@ -14,7 +14,7 @@ repos:
     - id: ruff-format
       types_or: [ python, pyi, jupyter ]
 -   repo: https://github.com/asottile/pyupgrade
-    rev: v3.19.0
+    rev: v3.19.1
     hooks:
     -   id: pyupgrade
         args: [--py311-plus]
diff --git a/benchmarks/configurations.py b/benchmarks/configurations.py
index e1e193c4e6d..62f52bf3181 100644
--- a/benchmarks/configurations.py
+++ b/benchmarks/configurations.py
@@ -90,14 +90,19 @@
             "seeds": 25,
             "replications": 3,
             "steps": 20,
-            "parameters": {"population": 200, "width": 100, "height": 100, "vision": 5},
+            "parameters": {
+                "population_size": 200,
+                "width": 100,
+                "height": 100,
+                "vision": 5,
+            },
         },
         "large": {
             "seeds": 10,
             "replications": 3,
             "steps": 10,
             "parameters": {
-                "population": 400,
+                "population_size": 400,
                 "width": 150,
                 "height": 150,
                 "vision": 15,
diff --git a/mesa/examples/basic/boid_flockers/agents.py b/mesa/examples/basic/boid_flockers/agents.py
index 2ff00cbaef2..10f31cdc530 100644
--- a/mesa/examples/basic/boid_flockers/agents.py
+++ b/mesa/examples/basic/boid_flockers/agents.py
@@ -6,10 +6,10 @@
 
 import numpy as np
 
-from mesa import Agent
+from mesa.experimental.continuous_space import ContinuousSpaceAgent
 
 
-class Boid(Agent):
+class Boid(ContinuousSpaceAgent):
     """A Boid-style flocker agent.
 
     The agent follows three behaviors to flock:
@@ -26,10 +26,12 @@ class Boid(Agent):
     def __init__(
         self,
         model,
-        speed,
-        direction,
-        vision,
-        separation,
+        space,
+        position=(0, 0),
+        speed=1,
+        direction=(1, 1),
+        vision=1,
+        separation=1,
         cohere=0.03,
         separate=0.015,
         match=0.05,
@@ -46,7 +48,8 @@ def __init__(
             separate: Relative importance of avoiding close neighbors (default: 0.015)
             match: Relative importance of matching neighbors' directions (default: 0.05)
         """
-        super().__init__(model)
+        super().__init__(space, model)
+        self.position = position
         self.speed = speed
         self.direction = direction
         self.vision = vision
@@ -58,46 +61,31 @@ def __init__(
 
     def step(self):
         """Get the Boid's neighbors, compute the new vector, and move accordingly."""
-        self.neighbors = self.model.space.get_neighbors(self.pos, self.vision, False)
+        neighbors, distances = self.get_neighbors_in_radius(radius=self.vision)
+        self.neighbors = [n for n in neighbors if n is not self]
 
         # If no neighbors, maintain current direction
-        if not self.neighbors:
-            new_pos = self.pos + self.direction * self.speed
-            self.model.space.move_agent(self, new_pos)
+        if not neighbors:
+            self.position += self.direction * self.speed
             return
 
-        # Initialize vectors for the three flocking behaviors
-        cohere = np.zeros(2)  # Cohesion vector
-        match_vector = np.zeros(2)  # Alignment vector
-        separation_vector = np.zeros(2)  # Separation vector
+        delta = self.space.calculate_difference_vector(self.position, agents=neighbors)
 
-        # Calculate the contribution of each neighbor to the three behaviors
-        for neighbor in self.neighbors:
-            heading = self.model.space.get_heading(self.pos, neighbor.pos)
-            distance = self.model.space.get_distance(self.pos, neighbor.pos)
-
-            # Cohesion - steer towards the average position of neighbors
-            cohere += heading
-
-            # Separation - avoid getting too close
-            if distance < self.separation:
-                separation_vector -= heading
-
-            # Alignment - match neighbors' flying direction
-            match_vector += neighbor.direction
-
-        # Weight each behavior by its factor and normalize by number of neighbors
-        n = len(self.neighbors)
-        cohere = cohere * self.cohere_factor
-        separation_vector = separation_vector * self.separate_factor
-        match_vector = match_vector * self.match_factor
+        cohere_vector = delta.sum(axis=0) * self.cohere_factor
+        separation_vector = (
+            -1 * delta[distances < self.separation].sum(axis=0) * self.separate_factor
+        )
+        match_vector = (
+            np.asarray([n.direction for n in neighbors]).sum(axis=0) * self.match_factor
+        )
 
         # Update direction based on the three behaviors
-        self.direction += (cohere + separation_vector + match_vector) / n
+        self.direction += (cohere_vector + separation_vector + match_vector) / len(
+            neighbors
+        )
 
         # Normalize direction vector
         self.direction /= np.linalg.norm(self.direction)
 
         # Move boid
-        new_pos = self.pos + self.direction * self.speed
-        self.model.space.move_agent(self, new_pos)
+        self.position += self.direction * self.speed
diff --git a/mesa/examples/basic/boid_flockers/app.py b/mesa/examples/basic/boid_flockers/app.py
index 16b1ccf7f68..174e2736a2a 100644
--- a/mesa/examples/basic/boid_flockers/app.py
+++ b/mesa/examples/basic/boid_flockers/app.py
@@ -1,3 +1,8 @@
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath("../../../.."))
+
 from mesa.examples.basic.boid_flockers.model import BoidFlockers
 from mesa.visualization import Slider, SolaraViz, make_space_component
 
@@ -17,7 +22,7 @@ def boid_draw(agent):
         "value": 42,
         "label": "Random Seed",
     },
-    "population": Slider(
+    "population_size": Slider(
         label="Number of boids",
         value=100,
         min=10,
diff --git a/mesa/examples/basic/boid_flockers/model.py b/mesa/examples/basic/boid_flockers/model.py
index 5b4974f3a20..c5d46a12b85 100644
--- a/mesa/examples/basic/boid_flockers/model.py
+++ b/mesa/examples/basic/boid_flockers/model.py
@@ -5,11 +5,17 @@
 Uses numpy arrays to represent vectors.
 """
 
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath("../../../.."))
+
+
 import numpy as np
 
 from mesa import Model
 from mesa.examples.basic.boid_flockers.agents import Boid
-from mesa.space import ContinuousSpace
+from mesa.experimental.continuous_space import ContinuousSpace
 
 
 class BoidFlockers(Model):
@@ -17,7 +23,7 @@ class BoidFlockers(Model):
 
     def __init__(
         self,
-        population=100,
+        population_size=100,
         width=100,
         height=100,
         speed=1,
@@ -31,7 +37,7 @@ def __init__(
         """Create a new Boids Flocking model.
 
         Args:
-            population: Number of Boids in the simulation (default: 100)
+            population_size: Number of Boids in the simulation (default: 100)
             width: Width of the space (default: 100)
             height: Height of the space (default: 100)
             speed: How fast the Boids move (default: 1)
@@ -44,48 +50,35 @@ def __init__(
         """
         super().__init__(seed=seed)
 
-        # Model Parameters
-        self.population = population
-        self.vision = vision
-        self.speed = speed
-        self.separation = separation
-
         # Set up the space
-        self.space = ContinuousSpace(width, height, torus=True)
-
-        # Store flocking weights
-        self.factors = {"cohere": cohere, "separate": separate, "match": match}
+        self.space = ContinuousSpace(
+            [[0, width], [0, height]],
+            torus=True,
+            random=self.random,
+            n_agents=population_size,
+        )
 
         # Create and place the Boid agents
-        self.make_agents()
+        positions = self.rng.random(size=(population_size, 2)) * self.space.size
+        directions = self.rng.uniform(-1, 1, size=(population_size, 2))
+        Boid.create_agents(
+            self,
+            population_size,
+            self.space,
+            position=positions,
+            direction=directions,
+            cohere=cohere,
+            separate=separate,
+            match=match,
+            speed=speed,
+            vision=vision,
+            separation=separation,
+        )
 
         # For tracking statistics
         self.average_heading = None
         self.update_average_heading()
 
-    def make_agents(self):
-        """Create and place all Boid agents randomly in the space."""
-        for _ in range(self.population):
-            # Random position
-            x = self.random.random() * self.space.x_max
-            y = self.random.random() * self.space.y_max
-            pos = np.array((x, y))
-
-            # Random initial direction
-            direction = np.random.random(2) * 2 - 1  # Random vector between -1 and 1
-            direction /= np.linalg.norm(direction)  # Normalize
-
-            # Create and place the Boid
-            boid = Boid(
-                model=self,
-                speed=self.speed,
-                direction=direction,
-                vision=self.vision,
-                separation=self.separation,
-                **self.factors,
-            )
-            self.space.place_agent(boid, pos)
-
     def update_average_heading(self):
         """Calculate the average heading (direction) of all Boids."""
         if not self.agents:
diff --git a/mesa/experimental/__init__.py b/mesa/experimental/__init__.py
index 946b2ba53fc..8f0ac91df48 100644
--- a/mesa/experimental/__init__.py
+++ b/mesa/experimental/__init__.py
@@ -15,6 +15,6 @@
     - Features graduate from experimental status once their APIs are stabilized
 """
 
-from mesa.experimental import cell_space, devs, mesa_signals
+from mesa.experimental import cell_space, continuous_space, devs, mesa_signals
 
-__all__ = ["cell_space", "devs", "mesa_signals"]
+__all__ = ["cell_space", "continuous_space", "devs", "mesa_signals"]
diff --git a/mesa/experimental/cell_space/voronoi.py b/mesa/experimental/cell_space/voronoi.py
index 92080d353cd..5c48f53af8a 100644
--- a/mesa/experimental/cell_space/voronoi.py
+++ b/mesa/experimental/cell_space/voronoi.py
@@ -186,7 +186,6 @@ def __init__(
         random: Random | None = None,
         cell_klass: type[Cell] = Cell,
         capacity_function: callable = round_float,
-        cell_coloring_property: str | None = None,
     ) -> None:
         """A Voronoi Tessellation Grid.
 
@@ -200,7 +199,7 @@ def __init__(
             random (Random): random number generator
             cell_klass (type[Cell]): type of cell class
             capacity_function (Callable): function to compute (int) capacity according to (float) area
-            cell_coloring_property (str): voronoi visualization polygon fill property
+
         """
         super().__init__(capacity=capacity, random=random, cell_klass=cell_klass)
         self.centroids_coordinates = centroids_coordinates
@@ -215,7 +214,6 @@ def __init__(
         self.triangulation = None
         self.voronoi_coordinates = None
         self.capacity_function = capacity_function
-        self.cell_coloring_property = cell_coloring_property
 
         self._connect_cells()
         self._build_cell_polygons()
@@ -266,4 +264,3 @@ def _build_cell_polygons(self):
             polygon_area = self._compute_polygon_area(polygon)
             self._cells[region].properties["area"] = polygon_area
             self._cells[region].capacity = self.capacity_function(polygon_area)
-            self._cells[region].properties[self.cell_coloring_property] = 0
diff --git a/mesa/experimental/continuous_space/__init__.py b/mesa/experimental/continuous_space/__init__.py
new file mode 100644
index 00000000000..13425cc0b8c
--- /dev/null
+++ b/mesa/experimental/continuous_space/__init__.py
@@ -0,0 +1,8 @@
+"""Continuous space support."""
+
+from mesa.experimental.continuous_space.continuous_space import ContinuousSpace
+from mesa.experimental.continuous_space.continuous_space_agents import (
+    ContinuousSpaceAgent,
+)
+
+__all__ = ["ContinuousSpace", "ContinuousSpaceAgent"]
diff --git a/mesa/experimental/continuous_space/continuous_space.py b/mesa/experimental/continuous_space/continuous_space.py
new file mode 100644
index 00000000000..232eb38414b
--- /dev/null
+++ b/mesa/experimental/continuous_space/continuous_space.py
@@ -0,0 +1,273 @@
+"""A Continuous Space class."""
+
+import warnings
+from collections.abc import Iterable
+from itertools import compress
+from random import Random
+
+import numpy as np
+from numpy.typing import ArrayLike
+from scipy.spatial.distance import cdist
+
+from mesa.agent import Agent, AgentSet
+
+
+class ContinuousSpace:
+    """Continuous space where each agent can have an arbitrary position."""
+
+    @property
+    def x_min(self):  # noqa: D102
+        # compatibility with solara_viz
+        return self.dimensions[0, 0]
+
+    @property
+    def x_max(self):  # noqa: D102
+        # compatibility with solara_viz
+        return self.dimensions[0, 1]
+
+    @property
+    def y_min(self):  # noqa: D102
+        # compatibility with solara_viz
+        return self.dimensions[1, 0]
+
+    @property
+    def y_max(self):  # noqa: D102
+        # compatibility with solara_viz
+        return self.dimensions[1, 1]
+
+    @property
+    def width(self):  # noqa: D102
+        # compatibility with solara_viz
+        return self.size[0]
+
+    @property
+    def height(self):  # noqa: D102
+        # compatibility with solara_viz
+        return self.size[1]
+
+    def __init__(
+        self,
+        dimensions: ArrayLike,
+        torus: bool = False,
+        random: Random | None = None,
+        n_agents: int = 100,
+    ) -> None:
+        """Create a new continuous space.
+
+        Args:
+            dimensions: a numpy array like object where each row specifies the minimum and maximum value of that dimension.
+            torus: boolean for whether the space wraps around or not
+            random: a seeded stdlib random.Random instance
+            n_agents: the expected number of agents in the space
+
+        Internally, a numpy array is used to store the positions of all agents. This is resized if needed,
+        but you can control the initial size explicitly by passing n_agents.
+
+
+        """
+        if random is None:
+            warnings.warn(
+                "Random number generator not specified, this can make models non-reproducible. Please pass a random number generator explicitly",
+                UserWarning,
+                stacklevel=2,
+            )
+            random = Random()
+        self.random = random
+
+        self.dimensions: np.array = np.asanyarray(dimensions)
+        self.ndims: int = self.dimensions.shape[0]
+        self.size: np.array = self.dimensions[:, 1] - self.dimensions[:, 0]
+        self.center: np.array = np.sum(self.dimensions, axis=1) / 2
+
+        self.torus: bool = torus
+
+        # self._agent_positions is the array containing all agent positions
+        # plus potential extra empty rows
+        # agent_positions is a view into _agent_positions containing only the filled rows
+        self._agent_positions: np.array = np.empty(
+            (n_agents, self.dimensions.shape[0]), dtype=float
+        )
+        self.agent_positions: (
+            np.array
+        )  # a view on _agent_positions containing all active positions
+
+        # the list of agents in the space
+        self.active_agents = []
+        self._n_agents = 0  # the number of active agents in the space
+
+        #  a mapping from agents to index and vice versa
+        self._index_to_agent: dict[int, Agent] = {}
+        self._agent_to_index: dict[Agent, int | None] = {}
+
+    @property
+    def agents(self) -> AgentSet:
+        """Return an AgentSet with the agents in the space."""
+        return AgentSet(self.active_agents, random=self.random)
+
+    def _add_agent(self, agent: Agent) -> int:
+        """Helper method for adding an agent to the space.
+
+        This method manages the numpy array with the agent positions and ensuring it is
+        enlarged if and when needed. It is called automatically by ContinousSpaceAgent when created.
+
+        """
+        index = self._n_agents
+        self._n_agents += 1
+
+        if self._agent_positions.shape[0] <= index:
+            # we are out of space
+            fraction = 0.2  # we add 20%  Fixme
+            n = int(round(fraction * self._n_agents))
+            self._agent_positions = np.vstack(
+                [
+                    self._agent_positions,
+                    np.empty(
+                        (n, self.dimensions.shape[0]),
+                    ),
+                ]
+            )
+
+        self._agent_to_index[agent] = index
+        self._index_to_agent[index] = agent
+
+        # we want to maintain a view rather than a copy on the active agents and positions
+        # this is essential for the performance of the rest of this code
+        self.active_agents.append(agent)
+        self.agent_positions = self._agent_positions[0 : self._n_agents]
+
+        return index
+
+    def _remove_agent(self, agent: Agent) -> None:
+        """Remove an agent from the space.
+
+        This method is automatically called by ContinuousSpaceAgent.remove.
+
+        """
+        index = self._agent_to_index[agent]
+        self._agent_to_index.pop(agent, None)
+        self._index_to_agent.pop(index, None)
+        del self.active_agents[index]
+
+        # we update all indices
+        for agent in self.active_agents[index::]:
+            old_index = self._agent_to_index[agent]
+            self._agent_to_index[agent] = old_index - 1
+            self._index_to_agent[old_index - 1] = agent
+
+        # we move all data below the removed agent one row up
+        self._agent_positions[index : self._n_agents - 1] = self._agent_positions[
+            index + 1 : self._n_agents
+        ]
+        self._n_agents -= 1
+        self.agent_positions = self._agent_positions[0 : self._n_agents]
+
+    def calculate_difference_vector(self, point: np.ndarray, agents=None) -> np.ndarray:
+        """Calculate the difference vector between the point and all agenents.
+
+        Args:
+            point: the point to calculate the difference vector for
+            agents: the agents to calculate the difference vector of point with. By default,
+                    all agents are considered.
+
+
+        """
+        point = np.asanyarray(point)
+        positions = (
+            self.agent_positions
+            if agents is None
+            else self._agent_positions[[self._agent_to_index[a] for a in agents]]
+        )
+
+        delta = positions - point
+
+        if self.torus:
+            inverse_delta = delta - np.sign(delta) * self.size
+
+            # we need to use the lowest absolute value from delta and inverse delta
+            logical = np.abs(delta) < np.abs(inverse_delta)
+
+            out = np.zeros(delta.shape)
+            out[logical] = delta[logical]
+            out[~logical] = inverse_delta[~logical]
+
+            delta = out
+
+        return delta
+
+    def calculate_distances(
+        self, point: ArrayLike, agents: Iterable[Agent] | None = None, **kwargs
+    ) -> tuple[np.ndarray, list]:
+        """Calculate the distance between the point and all agents.
+
+        Args:
+            point: the point to calculate the difference vector for
+            agents: the agents to calculate the difference vector of point with. By default,
+                    all agents are considered.
+            kwargs: any additional keyword arguments are passed to scipy's cdist, which is used
+                    only if torus is False. This allows for non-Euclidian distance measures.
+
+        """
+        point = np.asanyarray(point)
+
+        if agents is None:
+            positions = self.agent_positions
+            agents = self.active_agents
+        else:
+            positions = self._agent_positions[[self._agent_to_index[a] for a in agents]]
+            agents = np.asarray(agents)
+
+        if self.torus:
+            delta = np.abs(point - positions)
+            delta = np.minimum(delta, self.size - delta, out=delta)
+
+            # + is much faster than np.sum or array.sum
+            dists = delta[:, 0] ** 2
+            for i in range(1, self.ndims):
+                dists += delta[:, i] ** 2
+            dists = np.sqrt(dists)
+        else:
+            dists = cdist(point[np.newaxis, :], positions, **kwargs)[0, :]
+        return dists, agents
+
+    def get_agents_in_radius(
+        self, point: ArrayLike, radius: float | int = 1
+    ) -> tuple[list, np.ndarray]:
+        """Return the agents and their distances within a radius for the point."""
+        distances, agents = self.calculate_distances(point)
+        logical = distances <= radius
+        agents = list(compress(agents, logical))
+        return (
+            agents,
+            distances[logical],
+        )
+
+    def get_k_nearest_agents(
+        self, point: ArrayLike, k: int = 1
+    ) -> tuple[list, np.ndarray]:
+        """Return the k nearest agents and their distances to the point.
+
+        Notes:
+            This method returns exactly k agents, ignoring ties. In case of ties, the
+            earlier an agent is inserted the higher it will rank.
+
+        """
+        dists, agents = self.calculate_distances(point)
+
+        indices = np.argpartition(dists, k)[:k]
+        agents = [agents[i] for i in indices]
+        return agents, dists[indices]
+
+    def in_bounds(self, point: ArrayLike) -> bool:
+        """Check if point is inside the bounds of the space."""
+        return bool(
+            (
+                (np.asanyarray(point) >= self.dimensions[:, 0])
+                & (point <= self.dimensions[:, 1])
+            ).all()
+        )
+
+    def torus_correct(self, point: ArrayLike) -> np.ndarray:
+        """Apply a torus correction to the point."""
+        return self.dimensions[:, 0] + np.mod(
+            np.asanyarray(point) - self.dimensions[:, 0], self.size
+        )
diff --git a/mesa/experimental/continuous_space/continuous_space_agents.py b/mesa/experimental/continuous_space/continuous_space_agents.py
new file mode 100644
index 00000000000..f4ea9c2885f
--- /dev/null
+++ b/mesa/experimental/continuous_space/continuous_space_agents.py
@@ -0,0 +1,101 @@
+"""Continuous space agents."""
+
+from __future__ import annotations
+
+from itertools import compress
+from typing import Protocol
+
+import numpy as np
+
+from mesa.agent import Agent
+from mesa.experimental.continuous_space import ContinuousSpace
+
+
+class HasPositionProtocol(Protocol):
+    """Protocol for continuous space position holders."""
+
+    position: np.ndarray
+
+
+class ContinuousSpaceAgent(Agent):
+    """A continuous space agent.
+
+    Attributes:
+        space (ContinuousSpace): the continuous space in which the agent is located
+        position (np.ndarray): the position of the agent
+
+    """
+
+    __slots__ = ["_mesa_index", "space"]
+
+    @property
+    def position(self) -> np.ndarray:
+        """Position of the agent."""
+        return self.space.agent_positions[self.space._agent_to_index[self]]
+
+    @position.setter
+    def position(self, value: np.ndarray) -> None:
+        if not self.space.in_bounds(value):
+            if self.space.torus:
+                value = self.space.torus_correct(value)
+            else:
+                raise ValueError(f"point {value} is outside the bounds of the space")
+
+        self.space.agent_positions[self.space._agent_to_index[self]] = value
+
+    @property
+    def pos(self):  # noqa: D102
+        # just here for compatibility with solara_viz.
+        return self.position
+
+    @pos.setter
+    def pos(self, value):
+        # just here for compatibility solara_viz.
+        pass
+
+    def __init__(self, space: ContinuousSpace, model):
+        """Initialize a continuous space agent.
+
+        Args:
+            space: the continuous space in which the agent is located
+            model: the model to which the agent belongs
+
+        """
+        super().__init__(model)
+        self.space: ContinuousSpace = space
+        self.space._add_agent(self)
+        # self.position[:] = np.nan
+
+    def remove(self) -> None:
+        """Remove and delete the agent from the model and continuous space."""
+        super().remove()
+        self.space._remove_agent(self)
+        self._mesa_index = None
+        self.space = None
+
+    def get_neighbors_in_radius(
+        self, radius: float | int = 1
+    ) -> tuple[list, np.ndarray]:
+        """Get neighbors within radius.
+
+        Args:
+            radius: radius within which to look for neighbors
+
+        """
+        agents, dists = self.space.get_agents_in_radius(self.position, radius=radius)
+        logical = np.asarray([agent is not self for agent in agents])
+        agents = list(compress(agents, logical))
+        return agents, dists[logical]
+
+    def get_nearest_neighbors(self, k: int = 1) -> tuple[list, np.ndarray]:
+        """Get neighbors within radius.
+
+        Args:
+            k: the number of nearest neighbors to return
+
+        """
+        # return includes self, so we need to get k+1
+        agents, dists = self.space.get_k_nearest_agents(self.position, k=k + 1)
+        logical = np.asarray([agent is not self for agent in agents])
+        agents = list(compress(agents, logical))
+        return agents, dists[logical]
diff --git a/mesa/model.py b/mesa/model.py
index 8263926a851..c507ed73a10 100644
--- a/mesa/model.py
+++ b/mesa/model.py
@@ -94,7 +94,7 @@ def __init__(
             self._seed = seed  # this allows for reproducing stdlib.random
 
             try:
-                self.rng: np.random.Generator = np.random.default_rng(rng)
+                self.rng: np.random.Generator = np.random.default_rng(seed)
             except TypeError:
                 rng = self.random.randint(0, sys.maxsize)
                 self.rng: np.random.Generator = np.random.default_rng(rng)
diff --git a/mesa/space.py b/mesa/space.py
index 6e15426bf34..d503d42e41b 100644
--- a/mesa/space.py
+++ b/mesa/space.py
@@ -1415,6 +1415,13 @@ def get_neighbors(
                             coordinates. i.e. if you are searching for the
                             neighbors of a given agent, True will include that
                             agent in the results.
+
+        Notes:
+            If 1 or more agents are located on pos, include_center=False will remove all these agents
+            from the results. So, if you really want to get the neighbors of a given agent,
+            you should set include_center=True, and then filter the list of agents to remove
+            the given agent (i.e., self when calling it from an agent).
+
         """
         if self._agent_points is None:
             self._build_agent_cache()
diff --git a/mesa/visualization/mpl_space_drawing.py b/mesa/visualization/mpl_space_drawing.py
index 784d370eba0..b0e10d039ae 100644
--- a/mesa/visualization/mpl_space_drawing.py
+++ b/mesa/visualization/mpl_space_drawing.py
@@ -20,7 +20,7 @@
 from matplotlib.cm import ScalarMappable
 from matplotlib.collections import PatchCollection
 from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba
-from matplotlib.patches import RegularPolygon
+from matplotlib.patches import Polygon, RegularPolygon
 
 import mesa
 from mesa.experimental.cell_space import (
@@ -143,7 +143,10 @@ def draw_space(
             draw_orthogonal_grid(space, agent_portrayal, ax=ax, **space_drawing_kwargs)
         case mesa.space.NetworkGrid() | mesa.experimental.cell_space.Network():
             draw_network(space, agent_portrayal, ax=ax, **space_drawing_kwargs)
-        case mesa.space.ContinuousSpace():
+        case (
+            mesa.space.ContinuousSpace()
+            | mesa.experimental.continuous_space.ContinuousSpace()
+        ):
             draw_continuous_space(space, agent_portrayal, ax=ax)
         case VoronoiGrid():
             draw_voronoi_grid(space, agent_portrayal, ax=ax)
@@ -498,7 +501,11 @@ def draw_continuous_space(
 
 
 def draw_voronoi_grid(
-    space: VoronoiGrid, agent_portrayal: Callable, ax: Axes | None = None, **kwargs
+    space: VoronoiGrid,
+    agent_portrayal: Callable,
+    ax: Axes | None = None,
+    draw_grid: bool = True,
+    **kwargs,
 ):
     """Visualize a voronoi grid.
 
@@ -506,6 +513,7 @@ def draw_voronoi_grid(
         space: the space to visualize
         agent_portrayal: a callable that is called with the agent and returns a dict
         ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots
+        draw_grid: whether to draw the grid or not
         kwargs: additional keyword arguments passed to ax.scatter
 
     Returns:
@@ -538,16 +546,18 @@ def draw_voronoi_grid(
 
     _scatter(ax, arguments, **kwargs)
 
-    for cell in space.all_cells:
-        polygon = cell.properties["polygon"]
-        ax.fill(
-            *zip(*polygon),
-            alpha=min(1, cell.properties[space.cell_coloring_property]),
-            c="red",
-            zorder=0,
-        )  # Plot filled polygon
-        ax.plot(*zip(*polygon), color="black")  # Plot polygon edges in black
+    def setup_voroinoimesh(cells):
+        patches = []
+        for cell in cells:
+            patch = Polygon(cell.properties["polygon"])
+            patches.append(patch)
+        mesh = PatchCollection(
+            patches, edgecolor="k", facecolor=(1, 1, 1, 0), linestyle="dotted", lw=1
+        )
+        return mesh
 
+    if draw_grid:
+        ax.add_collection(setup_voroinoimesh(space.all_cells.cells))
     return ax
 
 
diff --git a/mesa/visualization/solara_viz.py b/mesa/visualization/solara_viz.py
index d01c66e5f94..f5fde84b1a3 100644
--- a/mesa/visualization/solara_viz.py
+++ b/mesa/visualization/solara_viz.py
@@ -52,6 +52,7 @@ def SolaraViz(
     | Literal["default"] = "default",
     *,
     play_interval: int = 100,
+    render_interval: int = 1,
     simulator: Simulator | None = None,
     model_params=None,
     name: str | None = None,
@@ -72,6 +73,8 @@ def SolaraViz(
             Defaults to "default", which uses the default Altair space visualization.
         play_interval (int, optional): Interval for playing the model steps in milliseconds.
             This controls the speed of the model's automatic stepping. Defaults to 100 ms.
+        render_interval (int, optional): Controls how often plots are updated during a simulation,
+            allowing users to skip intermediate steps and update graphs less frequently.
         simulator: A simulator that controls the model (optional)
         model_params (dict, optional): Parameters for (re-)instantiating a model.
             Can include user-adjustable parameters and fixed parameters. Defaults to None.
@@ -90,6 +93,8 @@ def SolaraViz(
           model instance is provided, it will be converted to a reactive model using `solara.use_reactive`.
         - The `play_interval` argument controls the speed of the model's automatic stepping. A lower
           value results in faster stepping, while a higher value results in slower stepping.
+        - The `render_interval` argument determines how often plots are updated during simulation. Higher values
+          reduce update frequency,resulting in faster execution.
     """
     if components == "default":
         components = [components_altair.make_altair_space()]
@@ -103,7 +108,7 @@ def SolaraViz(
     # set up reactive model_parameters shared by ModelCreator and ModelController
     reactive_model_parameters = solara.use_reactive({})
     reactive_play_interval = solara.use_reactive(play_interval)
-
+    reactive_render_interval = solara.use_reactive(render_interval)
     with solara.AppBar():
         solara.AppBarTitle(name if name else model.value.__class__.__name__)
 
@@ -117,11 +122,20 @@ def SolaraViz(
                 max=500,
                 step=10,
             )
+            solara.SliderInt(
+                label="Render Interval (steps)",
+                value=reactive_render_interval,
+                on_value=lambda v: reactive_render_interval.set(v),
+                min=1,
+                max=100,
+                step=2,
+            )
             if not isinstance(simulator, Simulator):
                 ModelController(
                     model,
                     model_parameters=reactive_model_parameters,
                     play_interval=reactive_play_interval,
+                    render_interval=reactive_render_interval,
                 )
             else:
                 SimulatorController(
@@ -129,6 +143,7 @@ def SolaraViz(
                     simulator,
                     model_parameters=reactive_model_parameters,
                     play_interval=reactive_play_interval,
+                    render_interval=reactive_render_interval,
                 )
         with solara.Card("Model Parameters"):
             ModelCreator(
@@ -189,6 +204,7 @@ def ModelController(
     *,
     model_parameters: dict | solara.Reactive[dict] = None,
     play_interval: int | solara.Reactive[int] = 100,
+    render_interval: int | solara.Reactive[int] = 1,
 ):
     """Create controls for model execution (step, play, pause, reset).
 
@@ -196,7 +212,7 @@ def ModelController(
         model: Reactive model instance
         model_parameters: Reactive parameters for (re-)instantiating a model.
         play_interval: Interval for playing the model steps in milliseconds.
-
+        render_interval: Controls how often the plots are updated during simulation steps.Higher value reduce update frequency.
     """
     playing = solara.use_reactive(False)
     running = solara.use_reactive(True)
@@ -215,9 +231,12 @@ async def step():
 
     @function_logger(__name__)
     def do_step():
-        """Advance the model by one step."""
-        model.value.step()
+        """Advance the model by the number of steps specified by the render_interval slider."""
+        for _ in range(render_interval.value):
+            model.value.step()
+
         running.value = model.value.running
+
         force_update()
 
     @function_logger(__name__)
@@ -259,6 +278,7 @@ def SimulatorController(
     *,
     model_parameters: dict | solara.Reactive[dict] = None,
     play_interval: int | solara.Reactive[int] = 100,
+    render_interval: int | solara.Reactive[int] = 1,
 ):
     """Create controls for model execution (step, play, pause, reset).
 
@@ -267,7 +287,11 @@ def SimulatorController(
         simulator: Simulator instance
         model_parameters: Reactive parameters for (re-)instantiating a model.
         play_interval: Interval for playing the model steps in milliseconds.
+        render_interval: Controls how often the plots are updated during simulation steps.Higher values reduce update frequency.
 
+    Notes:
+        The `step button` increments the step by the value specified in the `render_interval` slider.
+        This behavior ensures synchronization between simulation steps and plot updates.
     """
     playing = solara.use_reactive(False)
     running = solara.use_reactive(True)
@@ -285,8 +309,8 @@ async def step():
     )
 
     def do_step():
-        """Advance the model by one step."""
-        simulator.run_for(1)
+        """Advance the model by the number of steps specified by the render_interval slider."""
+        simulator.run_for(render_interval.value)
         running.value = model.value.running
         force_update()
 
@@ -390,7 +414,6 @@ def ModelCreator(
           or are dictionaries containing parameter details such as type, value, min, and max.
         - The `seed` argument ensures reproducibility by setting the initial seed for the model's random number generator.
         - The component provides an interface for adjusting user-defined parameters and reseeding the model.
-
     """
     if model_parameters is None:
         model_parameters = {}
diff --git a/pyproject.toml b/pyproject.toml
index 51265df9301..db68f3986c6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,6 +38,7 @@ dependencies = [
   "numpy",
   "pandas",
   "tqdm",
+  "scipy"
 ]
 dynamic = ["version"]
 


Test patch:
diff --git a/tests/test_continuous_space.py b/tests/test_continuous_space.py
new file mode 100644
index 00000000000..41d7ca33186
--- /dev/null
+++ b/tests/test_continuous_space.py
@@ -0,0 +1,432 @@
+"""Tests for continuous space."""
+
+import numpy as np
+import pytest
+
+from mesa import Model
+from mesa.experimental.continuous_space import ContinuousSpace, ContinuousSpaceAgent
+
+
+def test_continuous_space():
+    """Test ContinuousSpace class."""
+    model = Model(seed=42)
+
+    dimensions = np.asarray([[0, 1], [-1, 0]])
+    space = ContinuousSpace(dimensions, torus=False, random=model.random)
+
+    # check some default fields
+    assert space.ndims == 2
+    assert np.all(space.size == [1, 1])
+    assert np.all(space.center == [0.5, -0.5])
+    assert len(space.agents) == 0
+
+    # check in_bounds
+    assert space.in_bounds([0.5, -0.5])
+    assert not space.in_bounds([-0.5, -0.5])
+    assert not space.in_bounds([1.5, -0.5])
+    assert not space.in_bounds([0.5, 0.5])
+    assert not space.in_bounds([0.5, -1.5])
+
+    # check torus correction
+    space = ContinuousSpace(dimensions, torus=True, random=model.random)
+    assert np.all(space.torus_correct([-0.5, 0.5]) == [0.5, -0.5])
+    assert np.all(space.torus_correct([0.5, -0.5]) == [0.5, -0.5])
+    assert np.all(space.torus_correct([0.5, -0.5]) == [0.5, -0.5])
+    assert np.all(space.torus_correct([1.5, -0.5]) == [0.5, -0.5])
+    assert np.all(space.torus_correct([0.5, -1.5]) == [0.5, -0.5])
+
+    # check 3d
+    dimensions = np.asarray([[0, 2], [-2, 0], [-2, 2]])
+    space = ContinuousSpace(dimensions, torus=False, random=model.random)
+
+    # check some default fields
+    assert space.ndims == 3
+    assert np.all(space.size == [2, 2, 4])
+    assert np.all(space.center == [1, -1, 0])
+
+    # check in_bounds
+    assert space.in_bounds([1, -1, 0])
+    assert not space.in_bounds([-0.5, -1, 0])
+    assert not space.in_bounds([2.5, -1, 0])
+    assert not space.in_bounds([1, 0.5, 0])
+    assert not space.in_bounds([1, -2.5, 0])
+    assert not space.in_bounds([1, -1, -3])
+    assert not space.in_bounds([1, -1, 3])
+    assert not space.in_bounds([-0.5, -1, 3])
+    assert not space.in_bounds([1, 0.5, 3])
+
+
+def test_continuous_agent():
+    """Test ContinuousSpaceAgent class."""
+    model = Model(seed=42)
+
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=False, random=model.random)
+
+    for _ in range(10):
+        agent = ContinuousSpaceAgent(space, model)
+        position = [agent.random.random(), agent.random.random()]
+        agent.position = position
+        agent.coordinate = position
+
+    assert space.agent_positions.shape == (10, 2)
+    for agent in space.agents:
+        a = agent.position
+        b = space._agent_positions[space._agent_to_index[agent]]
+        assert np.all(a == b)
+        assert np.all(agent.position == agent.coordinate)
+
+    # add more agents, triggering a resizeing of the array
+    for _ in range(100):
+        agent = ContinuousSpaceAgent(space, model)
+        position = [agent.random.random(), agent.random.random()]
+        agent.position = position
+        agent.coordinate = position
+
+    assert space.agent_positions.shape == (110, 2)
+    for agent in space.agents:
+        a = agent.position
+        b = space._agent_positions[space._agent_to_index[agent]]
+        assert np.all(a == b)
+        assert np.all(agent.position == agent.coordinate)
+
+    # remove all agents and check if the view is updated throughout correctly
+    for i, agent in enumerate(space.agents):
+        assert np.all(
+            agent.position == agent.coordinate
+        )  ## check if updates of indices is correctly done
+        agent.remove()
+        assert space.agent_positions.shape == (110 - 1 - i, 2)
+
+    model = Model(seed=42)
+
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=True, random=model.random)
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [1.1, 1.1]
+    assert np.allclose(agent.position, [0.1, 0.1])
+
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=False, random=model.random)
+    agent = ContinuousSpaceAgent(space, model)
+    with pytest.raises(ValueError):
+        agent.position = [1.1, 1.1]
+
+
+def test_continous_space_calculate_distances():
+    """Test ContinuousSpace.distance method."""
+    # non torus
+    model = Model(seed=42)
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=False, random=model.random)
+
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [0.1, 0.1]
+
+    distances, agents = space.calculate_distances([0.1, 0.9])
+    assert np.all(
+        distances
+        == [
+            0.8,
+        ]
+    )
+    assert np.all(
+        agents
+        == [
+            agent,
+        ]
+    )
+
+    distances, agents = space.calculate_distances([0.9, 0.1])
+    assert np.all(
+        distances
+        == [
+            0.8,
+        ]
+    )
+    assert np.all(
+        agents
+        == [
+            agent,
+        ]
+    )
+
+    # torus
+    model = Model(seed=42)
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=True, random=model.random)
+
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [0.1, 0.1]
+
+    distances, agents = space.calculate_distances([0.1, 0.9])
+    assert np.all(
+        np.isclose(
+            distances,
+            [
+                0.2,
+            ],
+        )
+    )
+    assert np.all(
+        agents
+        == [
+            agent,
+        ]
+    )
+
+    distances, agents = space.calculate_distances([0.9, 0.1])
+    assert np.all(
+        np.isclose(
+            distances,
+            [
+                0.2,
+            ],
+        )
+    )
+    assert np.all(
+        agents
+        == [
+            agent,
+        ]
+    )
+
+    distances, agents = space.calculate_distances([0.9, 0.9])
+    assert np.all(
+        np.isclose(
+            distances,
+            [
+                0.2 * 2**0.5,
+            ],
+        )
+    )
+    assert np.all(
+        agents
+        == [
+            agent,
+        ]
+    )
+
+    distances, agents = space.calculate_distances(
+        [0.9, 0.9],
+        agents=[
+            agent,
+        ],
+    )
+    assert np.all(
+        np.isclose(
+            distances,
+            [
+                0.2 * 2**0.5,
+            ],
+        )
+    )
+    assert np.all(
+        agents
+        == [
+            agent,
+        ]
+    )
+
+
+def test_continous_space_difference_vector():
+    """Test ContinuousSpace.get_difference_vector method."""
+    # non torus
+    model = Model(seed=42)
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=False, random=model.random)
+
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [0.1, 0.1]
+
+    vector = space.calculate_difference_vector([0.1, 0.9])
+    assert np.all(vector == [0, -0.8])
+
+    vector = space.calculate_difference_vector([0.9, 0.1])
+    assert np.all(vector == [-0.8, 0])
+
+    # torus
+    model = Model(seed=42)
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=True, random=model.random)
+
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [0.1, 0.1]
+
+    vector = space.calculate_difference_vector([0.1, 0.9])
+    assert np.allclose(vector, [0, 0.2])
+
+    vector = space.calculate_difference_vector([0.9, 0.1])
+    assert np.allclose(vector, [0.2, 0])
+
+    vector = space.calculate_difference_vector([0.9, 0.9])
+    assert np.allclose(vector, [0.2, 0.2])
+
+
+def test_continuous_space_get_k_nearest_agents():  # noqa: D103
+    # non torus
+    model = Model(seed=42)
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=False, random=model.random)
+
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [0.1, 0.1]
+
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [0.1, 0.9]
+
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [0.9, 0.1]
+
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [0.9, 0.9]
+
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [0.5, 0.5]
+
+    agents, distances = space.get_k_nearest_agents([0.1, 0.1], k=1)
+    assert len(agents) == 1
+    assert np.allclose(
+        distances,
+        [
+            0,
+        ],
+    )
+
+    agents, distances = space.get_k_nearest_agents([0.5, 0.1], k=1)
+    assert len(agents) == 1
+    assert np.allclose(distances, [0.4, 0.4])
+
+    agents, distances = space.get_k_nearest_agents([0.5, 0.1], k=2)
+    assert len(agents) == 2
+    assert np.allclose(distances, [0.4, 0.4])
+
+    # torus
+    model = Model(seed=42)
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=True, random=model.random)
+
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [0.1, 0.1]
+
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [0.9, 0.1]
+
+    agent = ContinuousSpaceAgent(space, model)
+    agent.position = [0.9, 0.9]
+
+    agents, distances = space.get_k_nearest_agents([0.0, 0.1], k=2)
+    assert len(agents) == 2
+    assert np.allclose(distances, [0.1, 0.1])
+
+
+def test_continuous_space_get_agents_in_radius():  # noqa: D103
+    # non torus
+    model = Model(seed=42)
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=False, random=model.random)
+
+    positions = [
+        [0.1, 0.1],
+        [0.1, 0.9],
+        [0.9, 0.1],
+        [0.9, 0.9],
+        [0.5, 0.5],
+    ]
+
+    for position in positions:
+        agent = ContinuousSpaceAgent(space, model)
+        agent.position = position
+
+    agents, distances = space.get_agents_in_radius([0.1, 0.1], radius=0.1)
+    assert len(agents) == 1
+    assert np.allclose(
+        distances,
+        [
+            0,
+        ],
+    )
+
+    agents, distances = space.get_agents_in_radius([0.5, 0.1], radius=0.4)
+    assert len(agents) == 3
+    assert np.allclose(distances, [0.4, 0.4, 0.4])
+
+    agents, distances = space.get_agents_in_radius([0.5, 0.5], radius=1)
+    assert len(agents) == 5
+
+    # torus
+    model = Model(seed=42)
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=True, random=model.random)
+
+    positions = [
+        [0.1, 0.1],
+        [0.1, 0.9],
+        [0.9, 0.1],
+        [0.9, 0.9],
+        [0.5, 0.5],
+    ]
+
+    for position in positions:
+        agent = ContinuousSpaceAgent(space, model)
+        agent.position = position
+
+    agents, distances = space.get_agents_in_radius([0.0, 0.1], radius=0.1)
+    assert len(agents) == 2
+    assert np.allclose(distances, [0.1, 0.1])
+
+
+def test_get_neighbor_methos():  # noqa: D103
+    # non torus
+    model = Model(seed=42)
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=False, random=model.random)
+
+    positions = [
+        [0.1, 0.1],
+        [0.1, 0.9],
+        [0.9, 0.1],
+        [0.9, 0.9],
+        [0.5, 0.5],
+    ]
+
+    for position in positions:
+        agent = ContinuousSpaceAgent(space, model)
+        agent.position = position
+
+    agent: ContinuousSpaceAgent = model.agents[-1]  # 0.5, 0.5
+    agents, distances = agent.get_neighbors_in_radius(1)
+    assert len(agents) == 4
+
+    agents, distances = agent.get_neighbors_in_radius(0.1)
+    assert len(agents) == 0
+
+    agent: ContinuousSpaceAgent = model.agents[0]  # 0.1, 0.1
+    agents, distances = agent.get_nearest_neighbors(k=2)
+    assert len(agents) == 2
+
+    # torus
+    model = Model(seed=42)
+    dimensions = np.asarray([[0, 1], [0, 1]])
+    space = ContinuousSpace(dimensions, torus=True, random=model.random)
+
+    positions = [
+        [0.1, 0.1],
+        [0.1, 0.9],
+        [0.9, 0.1],
+        [0.9, 0.9],
+        [0.5, 0.5],
+    ]
+
+    for position in positions:
+        agent = ContinuousSpaceAgent(space, model)
+        agent.position = position
+
+    agent: ContinuousSpaceAgent = model.agents[-1]  # 0.5, 0.5
+    agents, distances = agent.get_neighbors_in_radius(1)
+    assert len(agents) == 4
+
+    agent: ContinuousSpaceAgent = model.agents[0]  # 0.1, 0.1
+    agents, distances = agent.get_nearest_neighbors(k=2)
+    assert len(agents) == 2
+    assert np.allclose(distances, [0.2, 0.2])
diff --git a/tests/test_model.py b/tests/test_model.py
index ff1fe297b6a..a7e054d12b3 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -1,5 +1,7 @@
 """Tests for model.py."""
 
+import numpy as np
+
 from mesa.agent import Agent, AgentSet
 from mesa.model import Model
 
@@ -37,6 +39,16 @@ def test_seed(seed=23):
     assert model2._seed == seed + 1
     assert model._seed == seed
 
+    assert Model(seed=42).random.random() == Model(seed=42).random.random()
+    assert np.all(
+        Model(seed=42).rng.random(
+            10,
+        )
+        == Model(seed=42).rng.random(
+            10,
+        )
+    )
+
 
 def test_reset_randomizer(newseed=42):
     """Test resetting the random seed on the model."""