From 07d3ce14e796e15e66dd40c8d8f12bf99738eec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Bastian?= Date: Wed, 8 Mar 2023 11:15:11 +0100 Subject: [PATCH] Partition: avoid tasks too close in time --- repartir_taches/entrypoint.py | 1 + repartir_taches/partition.py | 43 ++++++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/repartir_taches/entrypoint.py b/repartir_taches/entrypoint.py index 040f778..9028a65 100644 --- a/repartir_taches/entrypoint.py +++ b/repartir_taches/entrypoint.py @@ -75,6 +75,7 @@ def assigner_taches(root_task: Category | Task, group_count: int): multiplicity[t_id] = task.nb_groups repart: list[list[TaskId]] = partition( bin_count=group_count, + tasks=all_tasks, costs=costs, multiplicity=multiplicity, ) diff --git a/repartir_taches/partition.py b/repartir_taches/partition.py index 86c3249..da44a48 100644 --- a/repartir_taches/partition.py +++ b/repartir_taches/partition.py @@ -1,12 +1,16 @@ """ Implements Multiway number partitioning greedy algorithm """ import typing as t +import logging from sortedcontainers import SortedList +from .config import Task __all__ = ["TaskId", "partition"] TaskId = t.NewType("TaskId", int) +logger = logging.getLogger(__name__) + class PartitionException(Exception): """An exception occurring during partitioning""" @@ -36,7 +40,10 @@ class Bin: def partition( - bin_count: int, costs: dict[TaskId, int], multiplicity: dict[TaskId, int] + bin_count: int, + tasks: list[Task], + costs: dict[TaskId, int], + multiplicity: dict[TaskId, int], ) -> list[list[TaskId]]: """Partitions the tasks, each with cost `costs[i]`, into `bin_count` bins. Each task has multiplicity `multiplicity[i]`, copies of the same task being mutually @@ -50,22 +57,36 @@ def partition( ordered_tasks.sort(key=lambda x: costs[x], reverse=True) for task in ordered_tasks: - least_full: Bin - least_full_pos: int + possible_bins: list[int] = [] + min_possible: t.Optional[int] = None for pos, cur_bin in enumerate(bins): - if task not in cur_bin: - least_full = cur_bin - least_full_pos = pos + if min_possible is not None and cur_bin.cost > min_possible: break - else: + if task not in cur_bin: + if min_possible is None: + min_possible = cur_bin.cost + possible_bins.append(pos) + if not possible_bins: raise UnsolvableConflict( "Pas assez de groupes pour affecter la tâche " - + f"{task} {multiplicity[task]} fois." + + f"{tasks[task].qualified_name} {multiplicity[task]} fois." ) - del bins[least_full_pos] - least_full.add(task, costs[task]) - bins.add(least_full) + # Pick one of the groups -- maximize distance to other tasks + closest_assigned = [] + for cur_bin_id in possible_bins: + cur_bin = bins[cur_bin_id] + if cur_bin.elts: + closest_assigned.append(min((abs(elt - task) for elt in cur_bin.elts))) + else: + closest_assigned.append(len(costs) + 1) + + assign_to_bin_id, _ = max(enumerate(closest_assigned), key=lambda x: x[1]) + assign_to_bin = bins[possible_bins[assign_to_bin_id]] + + del bins[assign_to_bin_id] + assign_to_bin.add(task, costs[task]) + bins.add(assign_to_bin) out: list[list[TaskId]] = [] for cur_bin in bins: