@@ -49,6 +49,8 @@ def __init__(self, neighborhood_config: NeighborhoodConfig,
4949 self ._radius = self ._config .get_radius ()
5050 self ._neighborhood = self ._create_neighborhood ()
5151
52+ self ._force_slow_mode = False
53+
5254 @property
5355 def coordinate_data (self ):
5456 return self ._coordinate_data
@@ -71,31 +73,75 @@ def enough_coordinates_initialized(self) -> bool:
7173 """
7274 Returns true if enough coordinates inside of the neighborhood
7375 have been initialized. Else false
76+
77+ If the neighborhood is in slow mode, this means all adjacent neighbors
78+ must be visited
79+ """
80+ if self ._is_slow_mode ():
81+ return self ._are_all_adjacent_neighbors_visited ()
82+ else :
83+ min_initialized = self ._config .get_min_initialized ()
84+ num_initialized = len (self ._get_initialized_coordinates ())
85+ return num_initialized >= min_initialized
86+
87+ def force_slow_mode (self ):
7488 """
75- min_initialized = self . _config . get_min_initialized ()
76- num_initialized = len ( self . _get_initialized_coordinates ())
77- return num_initialized >= min_initialized
89+ When called, forces the neighborhood into slow mode
90+ """
91+ self . _force_slow_mode = True
7892
7993 def calculate_new_coordinate (self ,
8094 magnitude : int ,
8195 enable_clipping : bool = True ,
8296 clip_value : int = 2 ) -> Coordinate :
8397 """
8498 Based on the measurements in the neighborhood, determine where
85- the next location should be
99+ the next location should be.
100+
101+ If the neighborhood is in slow mode, return the best found measurement
102+ Otherwise calculate a new coordinate from the measurements
86103
87104 Parameters
88105 ----------
89106 magnitude
90107 How large of a step to take
91- disable_clipping
108+ enable_clipping
92109 Determines whether or not to clip the final step vector.
110+ clip_value
111+ What value to clip the vector at, if it is enabled
93112
94113 Returns
95114 -------
96115 new_coordinate
97116 The new coordinate computed based on the neighborhood measurements.
98117 """
118+
119+ if self ._is_slow_mode ():
120+ return self ._get_best_coordinate_found ()
121+ else :
122+ return self ._calculate_new_coordinate (magnitude , enable_clipping ,
123+ clip_value )
124+
125+ def _get_best_coordinate_found (self ) -> Coordinate :
126+ vectors , measurements = self ._get_measurements_passing_constraints ()
127+
128+ if len (vectors ) == 0 :
129+ return self ._home_coordinate
130+
131+ home_measurement = self ._get_home_measurement ()
132+
133+ if home_measurement .is_passing_constraints ():
134+ vectors .append (Coordinate ([0 ] * self ._config .get_num_dimensions ()))
135+ measurements .append (home_measurement )
136+
137+ _ , best_vector = sorted (zip (measurements , vectors ))[- 1 ]
138+
139+ best_coordinate = self ._home_coordinate + best_vector
140+ return best_coordinate
141+
142+ def _calculate_new_coordinate (self , magnitude , enable_clipping ,
143+ clip_value ) -> Coordinate :
144+
99145 step_vector = self ._get_step_vector () * magnitude
100146
101147 if enable_clipping :
@@ -133,14 +179,30 @@ def _clip_vector_values(self, vector: Coordinate,
133179
134180 if max_value > clip_value and max_value != 0 :
135181 for i in range (len (vector )):
136- vector [i ] = clip_value * vector [i ]/ max_value
182+ vector [i ] = clip_value * vector [i ] / max_value
137183 return vector
138184
139185 def pick_coordinate_to_initialize (self ) -> Optional [Coordinate ]:
140186 """
141187 Based on the initialized coordinate values, pick an unvisited
142188 coordinate to initialize next.
189+
190+ If the neighborhood is in slow mode, only pick from within the adjacent neighbors
143191 """
192+
193+ if self ._is_slow_mode ():
194+ return self ._pick_slow_mode_coordinate_to_initialize ()
195+ else :
196+ return self ._pick_fast_mode_coordinate_to_initialize ()
197+
198+ def _pick_slow_mode_coordinate_to_initialize (self ):
199+ for neighbor in self ._get_all_adjacent_neighbors ():
200+ if not self ._is_coordinate_visited (neighbor ):
201+ return neighbor
202+
203+ raise Exception ("Picking slow mode coordinate, but none are unvisited" )
204+
205+ def _pick_fast_mode_coordinate_to_initialize (self ):
144206 covered_values_per_dimension = self ._get_covered_values_per_dimension ()
145207
146208 max_num_uncovered = - 1
@@ -250,12 +312,11 @@ def _get_step_vector(self) -> Coordinate:
250312 step_vector
251313 a coordinate that tells the direction to move.
252314 """
253- home_measurement = self ._coordinate_data .get_measurement (
254- coordinate = self ._home_coordinate )
315+ home_measurement = self ._get_home_measurement ()
255316
256317 assert home_measurement is not None , "Home measurement cannot be NoneType."
257318
258- if home_measurement . is_passing_constraints ():
319+ if self . _is_home_passing_constraints ():
259320 return self ._optimize_for_objectives (home_measurement )
260321
261322 return self ._optimize_for_constraints (home_measurement )
@@ -428,3 +489,58 @@ def _get_num_uncovered_values(
428489 num_uncovered += 1
429490
430491 return num_uncovered
492+
493+ def _is_slow_mode (self ):
494+ if self ._force_slow_mode :
495+ return True
496+
497+ if not self ._is_home_measured ():
498+ return False
499+
500+ passing_vectors , _ = self ._get_measurements_passing_constraints ()
501+ all_vectors , _ = self ._get_all_visited_measurements ()
502+
503+ any_failing = len (all_vectors ) != len (passing_vectors )
504+ any_passing = len (passing_vectors ) != 0
505+ home_passing = self ._is_home_passing_constraints ()
506+
507+ return (home_passing and any_failing ) or (not home_passing and
508+ any_passing )
509+
510+ def _are_all_adjacent_neighbors_visited (self ):
511+ for neighbor in self ._get_all_adjacent_neighbors ():
512+ if not self ._is_coordinate_visited (neighbor ):
513+ return False
514+ return True
515+
516+ def _get_all_adjacent_neighbors (self ):
517+ adjacent_neighbors = []
518+
519+ for dim in range (self ._config .get_num_dimensions ()):
520+ dimension = self ._config .get_dimension (dim )
521+
522+ down_neighbor = Coordinate (self ._home_coordinate )
523+ down_neighbor [dim ] -= 1
524+ if down_neighbor [dim ] >= dimension .get_min_idx ():
525+ adjacent_neighbors .append (down_neighbor )
526+
527+ up_neighbor = Coordinate (self ._home_coordinate )
528+ up_neighbor [dim ] += 1
529+ if up_neighbor [dim ] <= dimension .get_max_idx ():
530+ adjacent_neighbors .append (up_neighbor )
531+
532+ return adjacent_neighbors
533+
534+ def _get_home_measurement (self ):
535+ return self ._coordinate_data .get_measurement (
536+ coordinate = self ._home_coordinate )
537+
538+ def _is_home_measured (self ):
539+ return self ._get_home_measurement () is not None
540+
541+ def _is_home_passing_constraints (self ):
542+ if not self ._is_home_measured ():
543+ raise Exception ("Can't check home passing if it isn't measured yet" )
544+
545+ home_measurement = self ._get_home_measurement ()
546+ return home_measurement .is_passing_constraints ()
0 commit comments