Skip to content

Commit

Permalink
Adapt Genetic Algorithm to multi-objective tasks (#76)
Browse files Browse the repository at this point in the history
 - Add NSGA2 selection in genetic algorithm to perform multi-objective optimization

 - Change return type of fitness functions to tuple

 - Add multiobjective fitness function TimeAndResourcesFitness

 - Add is_multiobjective and fitness_weights parameters in Genetic Scheduler

 - Add multiobjective scheduling methods to Genetic Scheduler

 - Add Individual and IndividialFitness classes to change them dynamically what can't be done through the deap.creator

 - Сhange the logic of genetic operators. Now they work with chromosomes, and individuals are then created in a genetic 
   algorithm

 - Replace HallOfFame by ParetoFront in genetic algorithm to perform multi-objective optimization. In the case of single- 
   objective optimization, the behavior will not change

 - Add make_offspring function to reduce code

 - Add test of multi-objective genetic scheduling
  • Loading branch information
Timotshak authored Jan 28, 2024
1 parent de322de commit 1ccc640
Show file tree
Hide file tree
Showing 9 changed files with 373 additions and 234 deletions.
3 changes: 3 additions & 0 deletions sampo/scheduler/genetic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
from sampo.scheduler.genetic.base import GeneticScheduler
from sampo.scheduler.genetic.converter import ScheduleGenerationScheme
from sampo.scheduler.genetic.operators import (TimeFitness, SumOfResourcesPeaksFitness, SumOfResourcesFitness,
TimeWithResourcesFitness, DeadlineResourcesFitness, DeadlineCostFitness,
TimeAndResourcesFitness)
134 changes: 100 additions & 34 deletions sampo/scheduler/genetic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from sampo.scheduler.base import Scheduler, SchedulerType
from sampo.scheduler.genetic.operators import FitnessFunction, TimeFitness
from sampo.scheduler.genetic.schedule_builder import build_schedule
from sampo.scheduler.genetic.schedule_builder import build_schedules
from sampo.scheduler.genetic.converter import ChromosomeType, ScheduleGenerationScheme
from sampo.scheduler.heft.base import HEFTScheduler, HEFTBetweenScheduler
from sampo.scheduler.lft.base import LFTScheduler
Expand Down Expand Up @@ -40,8 +40,10 @@ def __init__(self,
rand: Optional[random.Random] = None,
seed: Optional[float or None] = None,
n_cpu: int = 1,
weights: list[int] = None,
fitness_constructor: Callable[[Callable[[list[ChromosomeType]], list[Schedule]]], FitnessFunction] = TimeFitness,
weights: Optional[list[int] or None] = None,
fitness_constructor: Callable[
[Callable[[list[ChromosomeType]], list[Schedule]]], FitnessFunction] = TimeFitness,
fitness_weights: tuple[int | float, ...] = (-1,),
scheduler_type: SchedulerType = SchedulerType.Genetic,
resource_optimizer: ResourceOptimizer = IdentityResourceOptimizer(),
work_estimator: WorkTimeEstimator = DefaultWorkEstimator(),
Expand All @@ -59,6 +61,7 @@ def __init__(self,
self.size_of_population = size_of_population
self.rand = rand or random.Random(seed)
self.fitness_constructor = fitness_constructor
self.fitness_weights = fitness_weights
self.work_estimator = work_estimator
self.sgs_type = sgs_type

Expand Down Expand Up @@ -93,7 +96,7 @@ def get_params(self, works_count: int) -> tuple[float, float, float, int]:

mutate_resources = self.mutate_resources
if mutate_resources is None:
mutate_resources = 0.005
mutate_resources = 0.05

mutate_zones = self.mutate_zones
if mutate_zones is None:
Expand Down Expand Up @@ -232,40 +235,103 @@ def schedule_with_cache(self,
:param timeline:
:return:
"""
schedule, schedule_start_time, timeline, order_nodes = self._build_schedules(wg, contractors, landscape, spec,
assigned_parent_time, timeline,
is_multiobjective=False)[0]

if validate:
validate_schedule(schedule, wg, contractors)

return schedule, schedule_start_time, timeline, order_nodes

def schedule_multiobjective(self,
wg: WorkGraph,
contractors: list[Contractor],
spec: ScheduleSpec = ScheduleSpec(),
validate: bool = False,
start_time: Time = Time(0),
timeline: Timeline | None = None,
landscape: LandscapeConfiguration = LandscapeConfiguration()) \
-> list[Schedule]:
"""
Implementation of a multiobjective scheduling process
:return: list of pareto-efficient Schedules
"""
if wg is None or len(wg.nodes) == 0:
raise ValueError('None or empty WorkGraph')
if contractors is None or len(contractors) == 0:
raise ValueError('None or empty contractor list')
schedules = self.schedule_multiobjective_with_cache(wg, contractors, landscape, spec, validate, start_time,
timeline)
schedules = [schedule for schedule, _, _, _ in schedules]
return schedules

def schedule_multiobjective_with_cache(self,
wg: WorkGraph,
contractors: list[Contractor],
landscape: LandscapeConfiguration = LandscapeConfiguration(),
spec: ScheduleSpec = ScheduleSpec(),
validate: bool = False,
assigned_parent_time: Time = Time(0),
timeline: Timeline | None = None) \
-> list[tuple[Schedule, Time, Timeline, list[GraphNode]]]:
"""
Build pareto-efficient schedules for received graph of workers and return their current states
"""
schedules = self._build_schedules(wg, contractors, landscape, spec, assigned_parent_time, timeline,
is_multiobjective=True)

if validate:
for schedule, _, _, _ in schedules:
validate_schedule(schedule, wg, contractors)

return schedules

def _build_schedules(self,
wg: WorkGraph,
contractors: list[Contractor],
landscape: LandscapeConfiguration = LandscapeConfiguration(),
spec: ScheduleSpec = ScheduleSpec(),
assigned_parent_time: Time = Time(0),
timeline: Timeline | None = None,
is_multiobjective: bool = False) \
-> list[tuple[Schedule, Time, Timeline, list[GraphNode]]]:
init_schedules = GeneticScheduler.generate_first_population(wg, contractors, landscape, spec,
self.work_estimator, self._deadline, self._weights)

mutate_order, mutate_resources, mutate_zones, size_of_population = self.get_params(wg.vertex_count)
worker_pool = get_worker_contractor_pool(contractors)
deadline = None if self._optimize_resources else self._deadline

scheduled_works, schedule_start_time, timeline, order_nodes = build_schedule(wg,
contractors,
worker_pool,
size_of_population,
self.number_of_generation,
mutate_order,
mutate_resources,
mutate_zones,
init_schedules,
self.rand,
spec,
landscape,
self.fitness_constructor,
self.work_estimator,
self.sgs_type,
self._n_cpu,
assigned_parent_time,
timeline,
self._time_border,
self._max_plateau_steps,
self._optimize_resources,
deadline,
self._only_lft_initialization,
self._verbose)
schedule = Schedule.from_scheduled_works(scheduled_works.values(), wg)

if validate:
validate_schedule(schedule, wg, contractors)

return schedule, schedule_start_time, timeline, order_nodes
schedules = build_schedules(wg,
contractors,
worker_pool,
size_of_population,
self.number_of_generation,
mutate_order,
mutate_resources,
mutate_zones,
init_schedules,
self.rand,
spec,
landscape,
self.fitness_constructor,
self.fitness_weights,
self.work_estimator,
self.sgs_type,
self._n_cpu,
assigned_parent_time,
timeline,
self._time_border,
self._max_plateau_steps,
self._optimize_resources,
deadline,
self._only_lft_initialization,
is_multiobjective,
self._verbose)
schedules = [
(Schedule.from_scheduled_works(scheduled_works.values(), wg), schedule_start_time, timeline, order_nodes)
for scheduled_works, schedule_start_time, timeline, order_nodes in schedules]

return schedules
Loading

0 comments on commit 1ccc640

Please sign in to comment.