diff --git a/pyproject.toml b/pyproject.toml index 39f4bd7..cb77c3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "factryengine" -version = "0.3.2" +version = "0.3.3" description = "production / job shop / resource scheduler for Python" authors = ["Jacob Østergaard Nielsen "] license = "MIT" diff --git a/src/factryengine/scheduler/heuristic_solver/task_allocator.py b/src/factryengine/scheduler/heuristic_solver/task_allocator.py index b7084f5..0df038a 100644 --- a/src/factryengine/scheduler/heuristic_solver/task_allocator.py +++ b/src/factryengine/scheduler/heuristic_solver/task_allocator.py @@ -251,7 +251,7 @@ def _find_indexes(self, resource_intervals: np.ma.MaskedArray) -> int | None: is_last_window_start = True # Flag to indicate the start of a window # Iterate through the range between first and last index - for i in range(first_index, last_index + 1): + for i in range(first_index + 1, last_index + 1): current = resource_intervals[i] previous = resource_intervals[i - 1] if i > 0 else 0 next_value = resource_intervals[i + 1] if i < last_index else 0 @@ -288,7 +288,7 @@ def _find_indexes(self, resource_intervals: np.ma.MaskedArray) -> int | None: continue # Detect start of window using masks - if is_curr_masked and previous > 0 and next_value > 0 and not is_last_window_start: + if is_curr_masked and next_value > 0 and (previous > 0 or is_prev_masked) and not is_last_window_start: indexes.append(i) is_last_window_start = True continue diff --git a/src/factryengine/scheduler/heuristic_solver/window_manager.py b/src/factryengine/scheduler/heuristic_solver/window_manager.py index 32cb63e..4d32551 100644 --- a/src/factryengine/scheduler/heuristic_solver/window_manager.py +++ b/src/factryengine/scheduler/heuristic_solver/window_manager.py @@ -47,25 +47,71 @@ def update_resource_windows( """ Removes the allocated intervals from the resource windows. """ - for resource_id, trim_intervals in allocated_resource_windows_dict.items(): - if not trim_intervals: + for resource_id, intervals_to_remove in allocated_resource_windows_dict.items(): + if not intervals_to_remove: continue - # Get the earliest start and latest end of the intervals - combined_start = trim_intervals[0][0] - combined_end = trim_intervals[-1][1] + # Get the resource's current available windows (structured array) + resource_windows = self.resource_windows_dict[resource_id] - # Create a single trim interval - combined_trim_interval = (combined_start, combined_end) + # Remove each interval individually + for interval in intervals_to_remove: + resource_windows = self._remove_interval_from_windows(resource_windows, interval) - # Get the window to trim - window = self.resource_windows_dict[resource_id] + # Update the resource windows dict + self.resource_windows_dict[resource_id] = resource_windows - # Trim the window using the combined interval - self.resource_windows_dict[resource_id] = self._trim_window( - window, combined_trim_interval - ) + def _remove_interval_from_windows( + self, windows, interval_to_remove: tuple[int, int] + ) -> np.ndarray: + updated_windows = [] + remove_start, remove_end = interval_to_remove + + for window in windows: + window_start = window['start'] + window_end = window['end'] + + # No overlap + if remove_end <= window_start or remove_start >= window_end: + updated_windows.append(window) + continue + + # Interval completely covers the window + if remove_start <= window_start and remove_end >= window_end: + continue # Entire window is removed + + # Overlaps at the start + if remove_start <= window_start < remove_end < window_end: + new_window = window.copy() + new_window['start'] = remove_end + new_window['duration'] = new_window['end'] - new_window['start'] + updated_windows.append(new_window) + continue + + # Overlaps at the end + if window_start < remove_start < window_end <= remove_end: + new_window = window.copy() + new_window['end'] = remove_start + new_window['duration'] = new_window['end'] - new_window['start'] + updated_windows.append(new_window) + continue + + # Overlaps in the middle, split the window + if window_start < remove_start and window_end > remove_end: + # Create two new windows + window1 = window.copy() + window1['end'] = remove_start + window1['duration'] = window1['end'] - window1['start'] + + window2 = window.copy() + window2['start'] = remove_end + window2['duration'] = window2['end'] - window2['start'] + + updated_windows.extend([window1, window2]) + continue + + return np.array(updated_windows, dtype=window_dtype) def _create_resource_windows_dict(self) -> dict[int, np.ndarray]: