@@ -117,6 +117,76 @@ def get_constrained_sensors_indices_dataframe(x_min, x_max, y_min, y_max, df, **
117117 return idx_constrained
118118
119119
120+ def get_constrained_sensors_indices_distance (j , piv , r , nx , ny , all_sensors ):
121+ """
122+ Efficiently finds sensors within radius r of a given sensor.
123+
124+ Parameters
125+ ----------
126+ j : int
127+ Current iteration (0-indexed)
128+ piv : np.ndarray
129+ Array of sensor indices in order of placement
130+ r : float
131+ Radius constraint (minimum distance between sensors)
132+ nx, ny : int
133+ Grid dimensions
134+ all_sensors : np.ndarray
135+ Ranked list of sensor locations.
136+
137+ Returns
138+ -------
139+ idx_constrained : np.ndarray
140+ Array of sensor indices within radius r
141+ """
142+ sensor_idx = max (0 , j - 1 )
143+ current_sensor = piv [sensor_idx ]
144+ current_coords = np .unravel_index ([current_sensor ], (nx , ny ))
145+ x_cord , y_cord = current_coords [0 ][0 ], current_coords [1 ][0 ]
146+ sensor_coords = np .unravel_index (all_sensors , (nx , ny ))
147+ distances_sq = (sensor_coords [0 ] - x_cord ) ** 2 + (sensor_coords [1 ] - y_cord ) ** 2
148+ return all_sensors [distances_sq < r ** 2 ]
149+
150+
151+ def get_constrained_sensors_indices_distance_df (
152+ j , piv , r , df , all_sensors , X_axis , Y_axis
153+ ):
154+ """
155+ Efficiently finds sensors within radius r of a given sensor for DataFrame input.
156+
157+ Parameters
158+ ----------
159+ j : int
160+ Current iteration (0-indexed)
161+ piv : np.ndarray
162+ Array of sensor indices in order of placement
163+ r : float
164+ Radius constraint (minimum distance between sensors)
165+ df : pd.DataFrame
166+ DataFrame containing sensor coordinates
167+ all_sensors : np.ndarray
168+ Ranked list of sensor locations
169+ X_axis : str
170+ Column name for X coordinates in the DataFrame
171+ Y_axis : str
172+ Column name for Y coordinates in the DataFrame
173+
174+ Returns
175+ -------
176+ idx_constrained : np.ndarray
177+ Array of sensor indices within radius r
178+ """
179+ sensor_idx = max (0 , j - 1 )
180+ current_sensor = piv [sensor_idx ]
181+ current_x = df .loc [current_sensor , X_axis ]
182+ current_y = df .loc [current_sensor , Y_axis ]
183+ sensors_df = df .loc [all_sensors ]
184+ distances_sq = (sensors_df [X_axis ] - current_x ) ** 2 + (
185+ sensors_df [Y_axis ] - current_y
186+ ) ** 2
187+ return all_sensors [distances_sq .values < r ** 2 ]
188+
189+
120190def load_functional_constraints (functionHandler ):
121191 """
122192 Parameters:
@@ -555,32 +625,37 @@ def plot_selected_sensors(
555625 """
556626 n_samples , n_features = self .data .shape
557627 n_sensors = len (sensors )
558- constrained = sensors [np .where (~ np .isin (all_sensors [:n_sensors ], sensors ))[0 ]]
559- unconstrained = sensors [np .where (np .isin (all_sensors [:n_sensors ], sensors ))[0 ]]
628+ constrained = sensors [~ np .isin (sensors , all_sensors [:n_sensors ])]
629+ unconstrained = sensors [np .isin (sensors , all_sensors [:n_sensors ])]
630+
560631 if isinstance (self .data , np .ndarray ):
561632 xconst = np .mod (constrained , np .sqrt (n_features ))
562633 yconst = np .floor (constrained / np .sqrt (n_features ))
563634 xunconst = np .mod (unconstrained , np .sqrt (n_features ))
564635 yunconst = np .floor (unconstrained / np .sqrt (n_features ))
636+
565637 self .ax .plot (xconst , yconst , "*" , color = color_constrained )
566638 self .ax .plot (xunconst , yunconst , "*" , color = color_unconstrained )
639+
567640 elif isinstance (self .data , pd .DataFrame ):
568- constCoords = get_coordinates_from_indices (
641+ xconst , yconst = get_coordinates_from_indices (
569642 constrained ,
570643 self .data ,
571644 Y_axis = self .Y_axis ,
572645 X_axis = self .X_axis ,
573646 Field = self .Field ,
574647 )
575- unconstCoords = get_coordinates_from_indices (
648+
649+ xunconst , yunconst = get_coordinates_from_indices (
576650 unconstrained ,
577651 self .data ,
578652 Y_axis = self .Y_axis ,
579653 X_axis = self .X_axis ,
580654 Field = self .Field ,
581655 )
582- self .ax .plot (constCoords , "*" , color = color_constrained )
583- self .ax .plot (unconstCoords , "*" , color = color_unconstrained )
656+
657+ self .ax .plot (xconst , yconst , "*" , color = color_constrained )
658+ self .ax .plot (xunconst , yunconst , "*" , color = color_unconstrained )
584659
585660 def sensors_dataframe (self , sensors ):
586661 """
@@ -620,6 +695,7 @@ def annotate_sensors(
620695 """
621696 Function to annotate the sensor location on the grid while also plotting the
622697 sensor location
698+
623699 Attributes
624700 ----------
625701 sensors : np.darray,
@@ -639,29 +715,40 @@ def annotate_sensors(
639715 """
640716 n_samples , n_features = self .data .shape
641717 n_sensors = len (sensors )
642- constrained = sensors [np .where (~ np .isin (all_sensors [:n_sensors ], sensors ))[0 ]]
643- unconstrained = sensors [np .where (np .isin (all_sensors [:n_sensors ], sensors ))[0 ]]
718+
719+ # Fixed logic for finding constrained and unconstrained sensors
720+ constrained = sensors [~ np .isin (sensors , all_sensors [:n_sensors ])]
721+ unconstrained = sensors [np .isin (sensors , all_sensors [:n_sensors ])]
722+
644723 if isinstance (self .data , np .ndarray ):
645724 xTop = np .mod (sensors , np .sqrt (n_features ))
646725 yTop = np .floor (sensors / np .sqrt (n_features ))
726+
647727 xconst = np .mod (constrained , np .sqrt (n_features ))
648728 yconst = np .floor (constrained / np .sqrt (n_features ))
729+
649730 xunconst = np .mod (unconstrained , np .sqrt (n_features ))
650731 yunconst = np .floor (unconstrained / np .sqrt (n_features ))
732+
651733 data = np .vstack ([sensors , xTop , yTop ]).T # noqa:F841
734+
652735 self .ax .plot (xconst , yconst , "*" , color = color_constrained , alpha = 0.5 )
653736 self .ax .plot (xunconst , yunconst , "*" , color = color_unconstrained , alpha = 0.5 )
654- for ind , i in enumerate (range (len (xTop ))):
655- self .ax .annotate (
656- f"{ str (ind )} " ,
657- (xTop [i ], yTop [i ]),
658- xycoords = "data" ,
659- xytext = (- 20 , 20 ),
660- textcoords = "offset points" ,
661- color = "r" ,
662- fontsize = 12 ,
663- arrowprops = dict (arrowstyle = "->" , color = "black" ),
664- )
737+
738+ # Improved annotation logic with index checking
739+ for ind in range (len (sensors )):
740+ if ind < len (xTop ) and ind < len (yTop ): # Make sure index is in bounds
741+ self .ax .annotate (
742+ f"{ ind } " ,
743+ (xTop [ind ], yTop [ind ]),
744+ xycoords = "data" ,
745+ xytext = (- 20 , 20 ),
746+ textcoords = "offset points" ,
747+ color = "r" ,
748+ fontsize = 12 ,
749+ arrowprops = dict (arrowstyle = "->" , color = "black" ),
750+ )
751+
665752 elif isinstance (self .data , pd .DataFrame ):
666753 xTop , yTop = get_coordinates_from_indices (
667754 sensors ,
@@ -670,33 +757,41 @@ def annotate_sensors(
670757 X_axis = self .X_axis ,
671758 Field = self .Field ,
672759 )
760+
673761 xconst , yconst = get_coordinates_from_indices (
674762 constrained ,
675763 self .data ,
676764 Y_axis = self .Y_axis ,
677765 X_axis = self .X_axis ,
678766 Field = self .Field ,
679767 )
768+
680769 xunconst , yunconst = get_coordinates_from_indices (
681770 unconstrained ,
682771 self .data ,
683772 Y_axis = self .Y_axis ,
684773 X_axis = self .X_axis ,
685774 Field = self .Field ,
686775 )
776+
687777 self .ax .plot (xconst , yconst , "*" , color = color_constrained , alpha = 0.5 )
688778 self .ax .plot (xunconst , yunconst , "*" , color = color_unconstrained , alpha = 0.5 )
689- for _ , i in enumerate (range (len (sensors ))):
690- self .ax .annotate (
691- f"{ str (i )} " ,
692- (xTop [i ], yTop [i ]),
693- xycoords = "data" ,
694- xytext = (- 20 , 20 ),
695- textcoords = "offset points" ,
696- color = "r" ,
697- fontsize = 12 ,
698- arrowprops = dict (arrowstyle = "->" , color = "black" ),
699- )
779+
780+ # Improved annotation logic - check array lengths and indices
781+ for i in range (len (sensors )):
782+ if i < len (xTop ) and i < len (yTop ): # Make sure index is in bounds
783+ # Check that the coordinates are valid (not NaN)
784+ if np .isfinite (xTop [i ]) and np .isfinite (yTop [i ]):
785+ self .ax .annotate (
786+ f"{ i } " ,
787+ (xTop [i ], yTop [i ]),
788+ xycoords = "data" ,
789+ xytext = (- 20 , 20 ),
790+ textcoords = "offset points" ,
791+ color = "r" ,
792+ fontsize = 12 ,
793+ arrowprops = dict (arrowstyle = "->" , color = "black" ),
794+ )
700795
701796
702797class Circle (BaseConstraint ):
@@ -1235,33 +1330,45 @@ def constraint_function(self, coords):
12351330 """
12361331 Function to compute whether a certain point on the grid lies inside/outside
12371332 the defined constrained region
1333+
12381334 Attributes
12391335 ----------
1240- x : float,
1241- x coordinate of point on the grid being evaluated to check whether it lies
1242- inside or outside the constrained region
1243- y : float,
1244- y coordinate of point on the grid being evaluated to check whether it lies
1336+ coords : list or tuple
1337+ [x, y] coordinates of point on the grid being evaluated to check whether
1338+ it lies
12451339 inside or outside the constrained region
1340+
1341+ Returns
1342+ -------
1343+ bool
1344+ True if point satisfies the constraint (inside for "in", outside for "out"),
1345+ False otherwise
12461346 """
1347+ if len (coords ) != 2 :
1348+ raise ValueError ("coords must contain exactly 2 elements [x, y]" )
1349+
12471350 x , y = coords [:]
1248- # define point in polygon
12491351 polygon = self .xy_coords
12501352 n = len (polygon )
1353+
1354+ if n < 3 :
1355+ raise ValueError ("Polygon must have at least 3 vertices" )
12511356 inFlag = False
12521357
12531358 for i in range (n ):
12541359 x1 , y1 = polygon [i ]
12551360 x2 , y2 = polygon [(i + 1 ) % n ]
1256-
1257- if ( y1 < y and y2 >= y ) or ( y2 < y and y1 >= y ) :
1258- if x1 + (y - y1 ) / ( y2 - y1 ) * ( x2 - x1 ) < x :
1259- inFlag = not inFlag
1260-
1361+ if ( y1 > y ) != ( y2 > y ):
1362+ if y1 != y2 :
1363+ x_intersect = x1 + (y - y1 ) * ( x2 - x1 ) / ( y2 - y1 )
1364+ if x < x_intersect :
1365+ inFlag = not inFlag
12611366 if self .loc .lower () == "in" :
12621367 return not inFlag
12631368 elif self .loc .lower () == "out" :
12641369 return inFlag
1370+ else :
1371+ raise ValueError (f"Invalid constraint type: { self .loc } .Must be'in' or'out'" )
12651372
12661373
12671374class UserDefinedConstraints (BaseConstraint ):
@@ -1385,7 +1492,7 @@ def draw(self, ax, **kwargs):
13851492 self .all_sensors , self .data
13861493 )
13871494 for k in range (len (xValue )):
1388- G [k , i ] = eval (
1495+ G [k , i ] = not eval (
13891496 self .equations [i ], {"x" : xValue [k ], "y" : yValue [k ]}
13901497 )
13911498 idx_const , rank = (
0 commit comments