Skip to content

Commit

Permalink
various additional tests
Browse files Browse the repository at this point in the history
  • Loading branch information
quaquel committed Nov 24, 2024
1 parent 425bc9a commit a494335
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 28 deletions.
36 changes: 9 additions & 27 deletions mesa/experimental/cell_space/property_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,35 +92,20 @@ def set_cells(self, value, condition: Callable | None = None):
Args:
value: The value to be used for the update.
condition: (Optional) A callable (like a lambda function or a NumPy ufunc)
that returns a boolean array when applied to the data.
condition: (Optional) A callable that returns a boolean array when applied to the data.
"""
if condition is None:
np.copyto(self.data, value) # In-place update
else:
if isinstance(condition, np.ufunc):
# Directly apply NumPy ufunc
condition_result = condition(self.data)
else:
# Vectorize non-ufunc conditions
vectorized_condition = np.vectorize(condition)
condition_result = vectorized_condition(self.data)

if (
not isinstance(condition_result, np.ndarray)
or condition_result.shape != self.data.shape
):
raise ValueError(
"Result of condition must be a NumPy array with the same shape as the grid."
)

vectorized_condition = np.vectorize(condition)
condition_result = vectorized_condition(self.data)
np.copyto(self.data, value, where=condition_result)

def modify_cells(
self,
operation: Callable,
value=None,
condition_function: Callable | None = None,
condition: Callable | None = None,
):
"""Modify cells using an operation, which can be a lambda function or a NumPy ufunc.
Expand All @@ -129,17 +114,14 @@ def modify_cells(
Args:
operation: A function to apply. Can be a lambda function or a NumPy ufunc.
value: The value to be used if the operation is a NumPy ufunc. Ignored for lambda functions.
condition_function: (Optional) A callable that returns a boolean array when applied to the data.
condition: (Optional) A callable that returns a boolean array when applied to the data.
"""
condition_array = np.ones_like(
self.data, dtype=bool
) # Default condition (all cells)
if condition_function is not None:
if isinstance(condition_function, np.ufunc):
condition_array = condition_function(self.data)
else:
vectorized_condition = np.vectorize(condition_function)
condition_array = vectorized_condition(self.data)
if condition is not None:
vectorized_condition = np.vectorize(condition)
condition_array = vectorized_condition(self.data)

# Check if the operation is a lambda function or a NumPy ufunc
if isinstance(operation, np.ufunc):
Expand Down Expand Up @@ -176,7 +158,7 @@ def select_cells(self, condition: Callable, return_list=True):
else:
return condition_array

def aggregate_property(self, operation: Callable):
def aggregate(self, operation: Callable):
"""Perform an aggregate operation (e.g., sum, mean) on a property across all cells.
Args:
Expand Down
50 changes: 49 additions & 1 deletion tests/test_cell_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ def test_select_cells():
assert mask.shape == (5, 5)
assert np.all(mask == (data > 0.5))

# fixme add extreme_values heighest and lowest
# fixme add extreme_values highest and lowest
mask = grid.select_cells(
extreme_values={"elevation": "highest"}, return_list=False, only_empty=False
)
Expand All @@ -753,6 +753,51 @@ def test_select_cells():
# fixme add pre-specified mask to any other option


def test_property_layer():
"""Test various property layer methods."""
elevation = PropertyLayer("elevation", (5,5), default_value=0.0)

# test set_cells
elevation.set_cells(10)
assert np.all(elevation.data==10)

elevation.set_cells(np.ones((5,5)))
assert np.all(elevation.data==1)

with pytest.raises(ValueError):
elevation.set_cells(np.ones((6, 6)))

data = np.random.default_rng(42).random((5, 5))
layer = PropertyLayer.from_data("some_name", data)

def condition(x):
return x > 0.5

layer.set_cells(1, condition=condition)
assert np.all((layer.data == 1) == (data > 0.5))

# modify_cells
layer.data = np.zeros((10, 10))
layer.modify_cells(lambda x: x + 2)
assert np.all(layer.data == 2)

layer.data = np.ones((10, 10))
layer.modify_cells(np.multiply, 3)
assert np.all(layer.data[3, 3] == 3)

data = np.random.default_rng(42).random((5, 5))
layer.data = np.random.default_rng(42).random((5, 5))
layer.modify_cells(np.add, value=3, condition=condition)
assert np.all((layer.data > 3.5) == (data > 0.5))

with pytest.raises(ValueError):
layer.modify_cells(np.add) # Missing value for ufunc

# aggregate
layer.data = np.ones((10, 10))
assert layer.aggregate(np.sum) == 100


def test_property_layer_errors():
"""Test error handling for PropertyLayers."""
dimensions = 5, 5
Expand All @@ -774,6 +819,9 @@ def test_property_layer_errors():
):
grid.add_property_layer(elevation)

with pytest.warns(UserWarning):
PropertyLayer("elevation", (10, 10), default_value=0, dtype=float)


def test_cell_agent(): # noqa: D103
cell1 = Cell((1,), capacity=None, random=random.Random())
Expand Down

0 comments on commit a494335

Please sign in to comment.