@@ -647,17 +647,50 @@ def remove_property_layer(self, property_name: str):
647
647
raise ValueError (f"Property layer { property_name } does not exist." )
648
648
del self .properties [property_name ]
649
649
650
- def select_cells_multi_properties (self , conditions : dict ) -> List [Coordinate ]:
650
+ def get_neighborhood_mask (
651
+ self , pos : Coordinate , moore : bool , include_center : bool , radius : int
652
+ ) -> np .ndarray :
651
653
"""
652
- Select cells based on multiple property conditions using NumPy.
654
+ Generate a boolean mask representing the neighborhood.
655
+
656
+ Args:
657
+ pos (Coordinate): Center of the neighborhood.
658
+ moore (bool): True for Moore neighborhood, False for Von Neumann.
659
+ include_center (bool): Include the central cell in the neighborhood.
660
+ radius (int): The radius of the neighborhood.
661
+
662
+ Returns:
663
+ np.ndarray: A boolean mask representing the neighborhood.
664
+ """
665
+ neighborhood = self .get_neighborhood (pos , moore , include_center , radius )
666
+ mask = np .zeros ((self .width , self .height ), dtype = bool )
667
+
668
+ # Convert the neighborhood list to a NumPy array and use advanced indexing
669
+ coords = np .array (neighborhood )
670
+ mask [coords [:, 0 ], coords [:, 1 ]] = True
671
+ return mask
672
+
673
+ def select_cells_multi_properties (
674
+ self ,
675
+ conditions : dict ,
676
+ only_neighborhood : bool = False ,
677
+ pos : None | Coordinate = None ,
678
+ moore : bool = True ,
679
+ include_center : bool = False ,
680
+ radius : int = 1 ,
681
+ ) -> list [Coordinate ]:
682
+ """
683
+ Select cells based on multiple property conditions using NumPy, optionally within a neighborhood.
653
684
654
685
Args:
655
686
conditions (dict): A dictionary where keys are property names and values are
656
687
callables that take a single argument (the property value)
657
688
and return a boolean.
689
+ only_neighborhood (bool): If True, restrict selection to the neighborhood.
690
+ pos, moore, include_center, radius: Optional neighborhood parameters.
658
691
659
692
Returns:
660
- List[Coordinate]: A list of coordinates where the conditions are satisfied.
693
+ List[Coordinate]: Coordinates where conditions are satisfied.
661
694
"""
662
695
# Start with a mask of all True values
663
696
combined_mask = np .ones ((self .width , self .height ), dtype = bool )
@@ -669,50 +702,107 @@ def select_cells_multi_properties(self, conditions: dict) -> List[Coordinate]:
669
702
# Combine with the existing mask using logical AND
670
703
combined_mask = np .logical_and (combined_mask , prop_mask )
671
704
705
+ if only_neighborhood and pos is not None :
706
+ neighborhood_mask = self .get_neighborhood_mask (
707
+ pos , moore , include_center , radius
708
+ )
709
+ combined_mask = np .logical_and (combined_mask , neighborhood_mask )
710
+
672
711
# Extract coordinates from the combined mask
673
712
selected_cells = list (zip (* np .where (combined_mask )))
674
713
return selected_cells
675
714
676
- def move_agent_to_random_cell (self , agent : Agent , conditions : dict ) -> None :
715
+ def move_agent_to_random_cell (
716
+ self ,
717
+ agent : Agent ,
718
+ conditions : dict ,
719
+ only_neighborhood : bool = False ,
720
+ moore : bool = True ,
721
+ include_center : bool = False ,
722
+ radius : int = 1 ,
723
+ ) -> None :
677
724
"""
678
- Move an agent to a random cell that meets specified property conditions.
725
+ Move an agent to a random cell that meets specified property conditions, optionally within a neighborhood .
679
726
If no eligible cells are found, issue a warning and keep the agent in its current position.
680
727
681
728
Args:
682
729
agent (Agent): The agent to move.
683
730
conditions (dict): Conditions for selecting the cell.
731
+ only_neighborhood, moore, include_center, radius: Optional neighborhood parameters.
684
732
"""
685
- eligible_cells = self .select_cells_multi_properties (conditions )
733
+ pos = agent .pos if only_neighborhood else None
734
+ eligible_cells = self .select_cells_multi_properties (
735
+ conditions ,
736
+ only_neighborhood ,
737
+ pos ,
738
+ moore ,
739
+ include_center ,
740
+ radius ,
741
+ )
686
742
if not eligible_cells :
687
- warn (f"No eligible cells found. Agent { agent .unique_id } remains in the current position." , RuntimeWarning )
743
+ warn (
744
+ f"No eligible cells found. Agent { agent .unique_id } remains in the current position." ,
745
+ RuntimeWarning , stacklevel = 2
746
+ )
688
747
return # Agent stays in the current position
689
748
690
749
# Randomly choose one of the eligible cells and move the agent
691
750
new_pos = agent .random .choice (eligible_cells )
692
751
self .move_agent (agent , new_pos )
693
752
694
- def move_agent_to_extreme_value_cell (self , agent : Agent , property_name : str , mode : str ) -> None :
753
+ def move_agent_to_extreme_value_cell (
754
+ self ,
755
+ agent : Agent ,
756
+ property_name : str ,
757
+ mode : str ,
758
+ only_neighborhood : bool = False ,
759
+ moore : bool = True ,
760
+ include_center : bool = False ,
761
+ radius : int = 1 ,
762
+ ) -> None :
695
763
"""
696
- Move an agent to a cell with the highest, lowest, or closest property value.
764
+ Move an agent to a cell with the highest, lowest, or closest property value,
765
+ optionally within a neighborhood.
697
766
698
767
Args:
699
768
agent (Agent): The agent to move.
700
769
property_name (str): The name of the property layer.
701
770
mode (str): 'highest', 'lowest', or 'closest'.
771
+ only_neighborhood, moore, include_center, radius: Optional neighborhood parameters.
702
772
"""
773
+ pos = agent .pos if only_neighborhood else None
703
774
prop_values = self .properties [property_name ].data
704
- if mode == 'highest' :
705
- target_value = np .max (prop_values )
706
- elif mode == 'lowest' :
707
- target_value = np .min (prop_values )
708
- elif mode == 'closest' :
775
+
776
+
777
+ if pos is not None :
778
+ # Mask out cells outside the neighborhood.
779
+ neighborhood_mask = self .get_neighborhood_mask (
780
+ pos , moore , include_center , radius
781
+ )
782
+ # Use NaN for out-of-neighborhood cells
783
+ masked_prop_values = np .where (neighborhood_mask , prop_values , np .nan )
784
+ else :
785
+ masked_prop_values = prop_values
786
+
787
+ # Find the target value
788
+ if mode == "highest" :
789
+ target_value = np .nanmax (masked_prop_values )
790
+ elif mode == "lowest" :
791
+ target_value = np .nanmin (masked_prop_values )
792
+ elif mode == "closest" :
709
793
agent_value = prop_values [agent .pos ]
710
- target_value = prop_values [np .abs (prop_values - agent_value ).argmin ()]
794
+ target_value = masked_prop_values [
795
+ np .nanargmin (np .abs (masked_prop_values - agent_value ))
796
+ ]
711
797
else :
712
- raise ValueError (f"Invalid mode { mode } . Choose from 'highest', 'lowest', or 'closest'." )
798
+ raise ValueError (
799
+ f"Invalid mode { mode } . Choose from 'highest', 'lowest', or 'closest'."
800
+ )
713
801
714
- target_cells = list (zip (* np .where (prop_values == target_value )))
715
- new_pos = agent .random .choice (target_cells )
802
+ # Find the coordinates of the target value(s)
803
+ target_cells = np .column_stack (np .where (masked_prop_values == target_value ))
804
+ # If there are multiple target cells, randomly choose one
805
+ new_pos = tuple (agent .random .choice (target_cells , axis = 0 ))
716
806
self .move_agent (agent , new_pos )
717
807
718
808
0 commit comments