Skip to content

Commit

Permalink
space: Implement PropertyLayer and _PropertyGrid (#1898)
Browse files Browse the repository at this point in the history
* space: Implement PropertyLayer and _PropertyGrid

* Allow initializing a _PropertyGrid with PropertyLayer

* _PropertyGrid: Implement multi-property cell selection and enhanced agent movement

This commit introduces three significant enhancements for grids based on the _PropertyGrid in the mesa.space module:
1. `select_cells_multi_properties`: Allows for the selection of cells based on multiple property conditions, using a combination of NumPy operations. This method returns a list of coordinates satisfying the specified conditions.
2. `move_agent_to_random_cell`: Enables moving an agent to a random cell that meets specified property conditions, enhancing the flexibility in agent movements.
3. `move_agent_to_extreme_value_cell`: Facilitates moving an agent to a cell with the highest, lowest, or closest property value, based on a specified mode. This method extends agent movement capabilities, allowing for more dynamic and condition-based relocations.

* _PropertyGrid: Add optional neighborhood filtering to spatial methods

- Updated `select_cells_multi_properties`, `move_agent_to_random_cell`, and `move_agent_to_extreme_value_cell` methods in the `_PropertyGrid` class to include an optional neighborhood filtering feature.
- Added `only_neighborhood` parameter to these methods to allow for conditional operations within a specified neighborhood around an agent's position.
- Introduced `get_neighborhood_mask` as a helper function to create a boolean mask for neighborhood-based selections, enhancing performance and readability.
- Modified methods to utilize NumPy for efficient array operations, improving the overall performance of grid-based spatial calculations.
- Ensured backward compatibility by setting `only_neighborhood` to `False` by default, allowing existing code to function without modification.

* PropertyLayer: Check dimensions and dtype on init

Checks on initialization are cheap, and it helps users make the right decision for (NumPy) data type.

* Add docstring to PropertyLayer

* tests: Add tests for PropertyLayer

* Add docstring for _PropertyGrid

* _PropertyGrid: Make _get_neighborhood_mask private

Mark the _get_neighborhood_mask method as private, since it's intended as a helper function for select_cells_multi_properties() and move_agent_to_random_cell()

* move_agent_to_extreme_value_cell: remove closest option

closest is more difficult than highest or lowest, since closest needs a target to compare against. For now too complex without proven need, so I removed it.

* tests: Add tests for _PropertyGrid

SingleGrid inherits from _PropertyGrid, so testing though SingleGrid.

* PropertyLayer: Fix handling of 'condition' callable in set_cells

Resolved an inconsistency in the set_cells method of our grid class. The method's documentation stated that the 'condition' argument should be a callable (such as a lambda function or a NumPy ufunc), but the implementation incorrectly treated 'condition' as a NumPy array.

This update rectifies the implementation to align with the documented behavior. Now, the 'condition' argument is correctly called with the grid data as input, and its output (expected to be a boolean array) is used for conditional in-place updates of the grid. This change ensures that the function operates correctly when provided with callable conditions, fulfilling the intended functionality.

Includes:
- Calling 'condition' with self.data and using its output for conditional updates.
- Adjusted error handling to check the output of the callable, ensuring it's a NumPy array with the correct shape.
- Updated comments within the method for clarity.

* PropertyLayer: Remove unnecessary check in aggregate_property

Whether it's a Lambda function or NumPy ufunc, they can be called the same way.

* _PropertyGrid: Take mask as input for selection functions

Take mask as input for selection functions. This way, it isn't needed to parse all neighbourhood elements. It also allows to use custom masks like for empty cells, etc.

get_neighborhood_mask is now a public (not private) method again.

* _PropertyGrid: Give option to return list or mask

Give the select_cells_multi_properties method an option to return a mask instead of a list of cells. list is still default.

* _PropertyGrid: Split move_agent_to_extreme_value_cell into two methods

Split the move_agent_to_extreme_value_cell into two functions:
- select_extreme_value_cells, which selects target cells
- move_agent_to_extreme_value_cell, which moves the agent to that cell

* _PropertyGrid: Add utility function to get empty mask

* _PropertyGrid: Update method docstring

* _PropertyGrid: Rename functions to select and move by multiple properties

Hopefully these names are a little clearer

* get_empty_mask: Use faster np.zeros

np.zeros is faster than np.full

* Optimize select_extreme_value_cells method for performance

This commit introduces several optimizations to the select_extreme_value_cells method of the _PropertyGrid class. These changes are aimed at enhancing the performance, especially for larger datasets, by reducing computational complexity and leveraging efficient numpy array operations.

Key Changes:
- Condensed the mask application process to a single conditional statement, reducing unnecessary operations when no mask is provided.
- Streamlined the calculation of extreme values (maximum or minimum) using direct numpy functions without separate branching.
- Optimized the creation of the target mask by utilizing numpy's inherent functions for array comparison, thereby minimizing the computational overhead.
- Improved the efficiency of converting the mask to a list of coordinates using np.argwhere and tolist(), which is more suited for numpy arrays.

These enhancements ensure that the method is more efficient and performant, particularly when handling large grids or property layers.

Fixes:
- The method now correctly returns a mask instead of an ndarray of coordinates when return_list is set to False. This fix aligns the method's behavior with its intended functionality and improves its usability in grid operations.

* Update PropertyGrid tests

* Update test_space.py

Fix remaining TestPropertyLayer

A condition always is a function, never a mask (for now).

* Add a test if the coordinate systems are identical

Checks if the property layer and the property grid use coordinates in the same way.

* PropertyLayer: Improve ufunc handling and lambda vectorization

This update significantly improves the PropertyLayer's efficiency and robustness, particularly in handling lambda functions and NumPy universal functions (ufuncs). It addresses a critical bug where lambda conditions were incorrectly returning single boolean values instead of boolean arrays (masks) required for grid operations. The enhancements include checks to optimize the use of ufuncs and ensure accurate application based on their argument requirements.

Motivation:
- The primary motivation for these changes was to fix a bug in the `set_cells` method where lambda functions used as conditions were returning single boolean values instead of boolean arrays. This behavior led to errors and inefficient grid updates.
- To address this, we optimized the method to correctly handle lambda functions and vectorize them only when necessary, ensuring they return a mask as required.
- Furthermore, the implementation was fine-tuned to better handle NumPy ufuncs, avoiding redundant vectorization and ensuring performance optimization.

Details:
- Enhanced `set_cells` to properly handle lambda conditions by vectorizing them to return a boolean array that matches the grid's shape.
- Added functionality to detect and directly apply NumPy ufuncs in both `set_cells` and `modify_cells`, bypassing unnecessary vectorization.
- Implemented a shape check in `set_cells` to ensure the condition results align with the grid's dimensions.
- Introduced `ufunc_requires_additional_input`, a utility function that determines if a ufunc needs an extra input, enhancing the accuracy of operations in `modify_cells`.
- Ensured that non-ufunc conditions and lambda functions are correctly handled with appropriate vectorization, maintaining backward compatibility and flexibility.
- The changes result in a more robust and efficient PropertyLayer, capable of handling a variety of functions with improved performance.

Impact:
These updates resolve the lambda function bug and significantly boost the PropertyLayer's performance and reliability, especially when dealing with different types of functions. The codebase is now better equipped to handle complex grid operations efficiently and accurately.

* Remove move functions from PropertGrid

As can be seen in the tests, movement can be done by selecting cells (with select_cells_by_properties and select_extreme_value_cells) and by then moving to them with the new move_agent_to_one_of.

* Black formatting, ruff fixes

* _Property_grid: Implement single select_cells method

Implement a single select_cells method that incorperates the functionality of both select_cells_by_properties and select_extreme_value_cells.

It now also allows multiple extreme values and multiple masks. Further, it adds a boolean to only include empty cells.

* _PropertyGrid: Keep empty_mask property, fix extreme_value bug

Keeps a property empty_mask, that's updated together with the empties property. But because it's a mask it's incredibly fast for other mask-based operations.

Also fix a bug in the extreme_value part of the select() method: It now only considers values from the mask.

* space: Mark PropertyLayer as experimental (add warning)

* Rename is_lambda_function -> is_single_argument_function

* Remove is_numpy_ufunc function abstraction

* fix: Handle all cases for modified_data in modify_cells

* Remove stray print function

* Remove unused PyLint directive

* PropertyLayer: Default to float64 (consistent with Python float)

* set_cells doc: Indicate that condition is optional

---------

Co-authored-by: rht <rhtbot@protonmail.com>
  • Loading branch information
EwoutH and rht committed Jan 6, 2024
1 parent 811b605 commit 0bd4429
Show file tree
Hide file tree
Showing 2 changed files with 831 additions and 6 deletions.
Loading

0 comments on commit 0bd4429

Please sign in to comment.