Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions examples/contractor_spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from random import Random

from sampo.scheduler import GeneticScheduler, TopologicalScheduler, RandomizedTopologicalScheduler, LFTScheduler, \
RandomizedLFTScheduler
from sampo.pipeline.lag_optimization import LagOptimizationStrategy
from sampo.generator.base import SimpleSynthetic
from sampo.generator.environment.contractor_by_wg import get_contractor_by_wg, ContractorGenerationMethod
from sampo.generator import SyntheticGraphType
from sampo.pipeline import SchedulingPipeline
from sampo.scheduler.heft.base import HEFTScheduler
from sampo.schemas.schedule_spec import ScheduleSpec

ss = SimpleSynthetic(rand=231)
rand = Random()
size = 100

wg = ss.work_graph(bottom_border=size - 5, top_border=size)

contractors = [get_contractor_by_wg(wg) for _ in range(10)]
spec = ScheduleSpec()

for node in wg.nodes:
if not node.is_inseparable_son():
selected_contractor_indices = rand.choices(list(range(len(contractors))),
k=rand.randint(1, len(contractors)))
spec.assign_contractors(node.id, {contractors[i].id for i in selected_contractor_indices})


scheduler = GeneticScheduler(number_of_generation=10)

project = SchedulingPipeline.create() \
.wg(wg) \
.contractors(contractors) \
.lag_optimize(LagOptimizationStrategy.TRUE) \
.spec(spec) \
.schedule(scheduler, validate=True) \
.visualization('2022-01-01')[0] \
.shape((14, 14)) \
.show_gant_chart()
4 changes: 2 additions & 2 deletions sampo/backend/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ def _ensure_toolbox_created(self):
from sampo.scheduler.genetic.utils import init_chromosomes_f, create_toolbox_using_cached_chromosomes

if self._init_schedules:
init_chromosomes = init_chromosomes_f(self._wg, self._contractors, self._init_schedules,
self._landscape)
init_chromosomes = init_chromosomes_f(self._wg, self._contractors, self._spec,
self._init_schedules, self._landscape)
else:
init_chromosomes = []

Expand Down
4 changes: 2 additions & 2 deletions sampo/backend/multiproc.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ def cache_genetic_info(self,
weights, init_schedules, assigned_parent_time, fitness_weights, sgs_type,
only_lft_initialization, is_multiobjective)
if init_schedules:
self._init_chromosomes = init_chromosomes_f(self._wg, self._contractors, init_schedules,
self._landscape)
self._init_chromosomes = init_chromosomes_f(self._wg, self._contractors, self._spec,
init_schedules, self._landscape)
else:
self._init_chromosomes = []
self._pool = None
Expand Down
3 changes: 3 additions & 0 deletions sampo/generator/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ def __init__(self, rand: int | Random | None = None) -> None:
else:
self._rand = Random(rand)

def get_rand(self):
return self._rand

def small_work_graph(self, cluster_name: str | None = 'C1') -> WorkGraph:
"""
Creates a small graph of works consisting of 30-50 vertices;
Expand Down
4 changes: 2 additions & 2 deletions sampo/scheduler/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def run_with_contractor(contractor: Contractor) -> tuple[Time, Time, list[Worker
assigned_parent_time, work_estimator)
return c_st, c_ft, workers

return run_contractor_search(contractors, run_with_contractor)
return run_contractor_search(contractors, spec, run_with_contractor)

return optimize_resources_def

Expand All @@ -126,7 +126,7 @@ def schedule_with_cache(self,
)

if validate:
validate_schedule(schedule, wg, contractors)
validate_schedule(schedule, wg, contractors, spec)

return [(schedule, schedule_start_time, timeline, ordered_nodes)]

Expand Down
11 changes: 6 additions & 5 deletions sampo/scheduler/genetic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,15 @@ def generate_first_population(wg: WorkGraph,

schedule, _, _, node_order = LFTScheduler(work_estimator=work_estimator).schedule_with_cache(wg, contractors,
spec,
landscape=landscape)[0]
landscape=landscape,
validate=True)[0]
init_lft_schedule = (schedule, node_order, spec)

def init_k_schedule(scheduler_class, k) -> tuple[Schedule | None, list[GraphNode] | None, ScheduleSpec | None]:
try:
schedule, _, _, node_order = (scheduler_class(work_estimator=work_estimator,
resource_optimizer=AverageReqResourceOptimizer(k))
.schedule_with_cache(wg, contractors, spec, landscape=landscape))[0]
.schedule_with_cache(wg, contractors, spec, landscape=landscape, validate=True))[0]
return schedule, node_order, spec
except NoSufficientContractorError:
return None, None, None
Expand All @@ -187,7 +188,7 @@ def init_k_schedule(scheduler_class, k) -> tuple[Schedule | None, list[GraphNode
def init_schedule(scheduler_class) -> tuple[Schedule | None, list[GraphNode] | None, ScheduleSpec | None]:
try:
schedule, _, _, node_order = (scheduler_class(work_estimator=work_estimator)
.schedule_with_cache(wg, contractors, spec, landscape=landscape))[0]
.schedule_with_cache(wg, contractors, spec, landscape=landscape, validate=True))[0]
return schedule, node_order, spec
except NoSufficientContractorError:
return None, None, None
Expand All @@ -197,7 +198,7 @@ def init_schedule(scheduler_class) -> tuple[Schedule | None, list[GraphNode] | N
try:
(schedule, _, _, node_order), modified_spec = AverageBinarySearchResourceOptimizingScheduler(
scheduler_class(work_estimator=work_estimator)
).schedule_with_cache(wg, contractors, deadline, spec, landscape=landscape)
).schedule_with_cache(wg, contractors, deadline, spec, landscape=landscape, validate=True)
return schedule, node_order, modified_spec
except NoSufficientContractorError:
return None, None, None
Expand Down Expand Up @@ -309,6 +310,6 @@ def schedule_with_cache(self,

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

return schedules
24 changes: 17 additions & 7 deletions sampo/scheduler/genetic/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from sampo.schemas.schedule_spec import ScheduleSpec
from sampo.schemas.time import Time
from sampo.schemas.time_estimator import WorkTimeEstimator, DefaultWorkEstimator
from sampo.utilities.collections_util import reverse_dictionary
from sampo.utilities.linked_list import LinkedList


Expand Down Expand Up @@ -63,16 +64,25 @@ def convert_schedule_to_chromosome(work_id2index: dict[str, int],
for node in order:
node_id = node.id
index = work_id2index[node_id]
for resource in schedule[node_id].workers:
res_count = resource.count
res_index = worker_name2index[resource.name]
res_contractor = resource.contractor_id
resource_chromosome[index, res_index] = res_count
resource_chromosome[index, -1] = contractor2index[res_contractor]

if schedule[node_id].workers:
for resource in schedule[node_id].workers:
res_count = resource.count
res_index = worker_name2index[resource.name]
res_contractor = resource.contractor_id

resource_chromosome[index, res_index] = res_count
resource_chromosome[index, -1] = contractor2index[res_contractor]
else:
contractor_list = spec[node_id].contractors
if not contractor_list:
contractor_list = contractor2index.keys()
random_contractor = list(contractor_list)[0]
resource_chromosome[index, -1] = contractor2index[random_contractor]

resource_border_chromosome = np.copy(contractor_borders)

return order_chromosome, resource_chromosome, resource_border_chromosome, spec, zone_changes_chromosome
return order_chromosome, resource_chromosome, resource_border_chromosome, copy.deepcopy(spec), zone_changes_chromosome


def convert_chromosome_to_schedule(chromosome: ChromosomeType,
Expand Down
67 changes: 48 additions & 19 deletions sampo/scheduler/genetic/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from sampo.schemas.landscape import LandscapeConfiguration
from sampo.schemas.resources import Worker
from sampo.schemas.schedule import Schedule
from sampo.schemas.schedule_spec import ScheduleSpec
from sampo.schemas.schedule_spec import ScheduleSpec, WorkSpec
from sampo.schemas.time import Time
from sampo.schemas.time_estimator import WorkTimeEstimator, DefaultWorkEstimator
from sampo.utilities.resource_usage import resources_peaks_sum, resources_costs_sum, resources_sum
Expand Down Expand Up @@ -165,6 +165,7 @@ def init_toolbox(wg: WorkGraph,
parents: dict[int, set[int]],
children: dict[int, set[int]],
resources_border: np.ndarray,
contractors_available: np.ndarray,
assigned_parent_time: Time = Time(0),
fitness_weights: tuple[int | float, ...] = (-1,),
work_estimator: WorkTimeEstimator = DefaultWorkEstimator(),
Expand Down Expand Up @@ -200,7 +201,8 @@ def init_toolbox(wg: WorkGraph,
# combined mutation
toolbox.register('mutate', mutate, order_mutpb=mut_order_pb, res_mutpb=mut_res_pb, zone_mutpb=mut_zone_pb,
rand=rand, parents=parents, children=children, resources_border=resources_border,
statuses_available=statuses_available, priorities=priorities)
contractors_available=contractors_available, statuses_available=statuses_available,
priorities=priorities)
# crossover for order
toolbox.register('mate_order', mate_scheduling_order, rand=rand, toolbox=toolbox, priorities=priorities)
# mutation for order
Expand All @@ -210,7 +212,7 @@ def init_toolbox(wg: WorkGraph,
toolbox.register('mate_resources', mate_resources, rand=rand, toolbox=toolbox)
# mutation for resources
toolbox.register('mutate_resources', mutate_resources, resources_border=resources_border,
mutpb=mut_res_pb, rand=rand)
contractors_available=contractors_available, mutpb=mut_res_pb, rand=rand)
# mutation for resource borders
toolbox.register('mutate_resource_borders', mutate_resource_borders, contractor_borders=contractor_borders,
mutpb=mut_res_pb, rand=rand)
Expand All @@ -219,7 +221,7 @@ def init_toolbox(wg: WorkGraph,
statuses_available=landscape.zone_config.statuses.statuses_available())

toolbox.register('validate', is_chromosome_correct, node_indices=node_indices, parents=parents,
contractor_borders=contractor_borders, index2node=index2node)
contractor_borders=contractor_borders, index2node=index2node, index2contractor=index2contractor_obj)
toolbox.register('schedule_to_chromosome', convert_schedule_to_chromosome,
work_id2index=work_id2index, worker_name2index=worker_name2index,
contractor2index=contractor2index, contractor_borders=contractor_borders, spec=spec,
Expand Down Expand Up @@ -324,9 +326,6 @@ def randomized_init(is_topological: bool = False) -> ChromosomeType:
case _:
ind = init_chromosomes[generated_type][0]

if not toolbox.validate(ind):
SAMPO.logger.warn('HELP')

ind = toolbox.Individual(ind)
chromosomes.append(ind)

Expand Down Expand Up @@ -390,12 +389,13 @@ def select_new_population(population: list[Individual], k: int) -> list[Individu


def is_chromosome_correct(ind: Individual, node_indices: list[int], parents: dict[int, set[int]],
contractor_borders: np.ndarray, index2node: dict[int, GraphNode]) -> bool:
contractor_borders: np.ndarray, index2node: dict[int, GraphNode],
index2contractor: dict[int, Contractor]) -> bool:
"""
Check correctness of works order and contractors borders.
"""
return is_chromosome_order_correct(ind, parents, index2node) and \
is_chromosome_contractors_correct(ind, node_indices, contractor_borders)
is_chromosome_contractors_correct(ind, node_indices, contractor_borders, index2node, index2contractor)


def is_chromosome_order_correct(ind: Individual, parents: dict[int, set[int]], index2node: dict[int, GraphNode]) -> bool:
Expand All @@ -419,13 +419,27 @@ def is_chromosome_order_correct(ind: Individual, parents: dict[int, set[int]], i


def is_chromosome_contractors_correct(ind: Individual, work_indices: Iterable[int],
contractor_borders: np.ndarray) -> bool:
contractor_borders: np.ndarray,
index2node: dict[int, GraphNode],
index2contractor: dict[int, Contractor]) -> bool:
"""
Checks that assigned contractors can supply assigned workers.
"""
if not work_indices:
return True

order = ind[0]
resources = ind[1][work_indices]

# check contractor align with the spec
spec: ScheduleSpec = ind[3]
contractors = resources[:, -1]
for i in range(len(order)):
work_index = order[i]
work_spec = spec[index2node[work_index].id]
if not work_spec.is_contractor_enabled(index2contractor[contractors[work_index]].id):
return False

# sort resource part of chromosome by contractor ids
resources = resources[resources[:, -1].argsort()]
# get unique contractors and indexes where they start
Expand Down Expand Up @@ -579,8 +593,8 @@ def mutate_scheduling_order(ind: Individual, mutpb: float, rand: random.Random,
"""
order = ind[0]

priority_groups_count = len(set(priorities))
mutpb_for_priority_group = mutpb #/ priority_groups_count
# priority_groups_count = len(set(priorities))
mutpb_for_priority_group = mutpb # / priority_groups_count

# priorities of tasks with same order-index should be the same (if chromosome is valid)
cur_priority = priorities[order[0]]
Expand Down Expand Up @@ -634,15 +648,17 @@ def mate_resources(ind1: Individual, ind2: Individual, rand: random.Random,


def mutate_resources(ind: Individual, mutpb: float, rand: random.Random,
resources_border: np.ndarray) -> Individual:
resources_border: np.ndarray,
contractors_available: np.ndarray) -> Individual:
"""
Mutation function for resources.
It changes selected numbers of workers in random work in a certain interval for this work.

:param ind: the individual to be mutated
:param resources_border: low and up borders of resources amounts
:param mutpb: probability of gene mutation
:param rand: the rand object used for randomized operations
:param resources_border: low and up borders of resources amounts
:param contractors_available: mask of contractors available to do tasks

:return: mutated individual
"""
Expand All @@ -654,9 +670,22 @@ def mutate_resources(ind: Individual, mutpb: float, rand: random.Random,
mask = np.array([rand.random() < mutpb for _ in range(num_works)])
if mask.any():
# generate new contractors in the number of received True values of mask
new_contractors = np.array([rand.randint(0, num_contractors - 1) for _ in range(mask.sum())])

# [rand.randint(0, num_contractors - 1) for _ in range(mask.sum())]
new_contractors_list = []

# TODO Rewrite to numpy functions if heavy
for task, task_selected in enumerate(mask):
if not task_selected:
continue
contractors_to_select = np.where(contractors_available[task] == 1)
new_contractors_list.append(rand.choices(contractors_to_select[0], k=1)[0])

new_contractors = np.array(new_contractors_list)

# obtain a new mask of correspondence
# between the borders of the received contractors and the assigned resources

contractor_mask = (res[mask, :-1] <= ind[2][new_contractors]).all(axis=1)
# update contractors by received mask
new_contractors = new_contractors[contractor_mask]
Expand Down Expand Up @@ -720,9 +749,9 @@ def mate(ind1: Individual, ind2: Individual, optimize_resources: bool,
return toolbox.Individual(child1), toolbox.Individual(child2)


def mutate(ind: Individual, resources_border: np.ndarray, parents: dict[int, set[int]],
children: dict[int, set[int]], statuses_available: int, priorities: np.ndarray,
order_mutpb: float, res_mutpb: float, zone_mutpb: float,
def mutate(ind: Individual, resources_border: np.ndarray, contractors_available: np.ndarray,
parents: dict[int, set[int]], children: dict[int, set[int]], statuses_available: int,
priorities: np.ndarray, order_mutpb: float, res_mutpb: float, zone_mutpb: float,
rand: random.Random) -> Individual:
"""
Combined mutation function of mutation for order, mutation for resources and mutation for zones.
Expand All @@ -740,7 +769,7 @@ def mutate(ind: Individual, resources_border: np.ndarray, parents: dict[int, set
:return: mutated individual
"""
mutant = mutate_scheduling_order(ind, order_mutpb, rand, priorities, parents, children)
mutant = mutate_resources(mutant, res_mutpb, rand, resources_border)
mutant = mutate_resources(mutant, res_mutpb, rand, resources_border, contractors_available)
# TODO Make better mutation for zones and uncomment this
# mutant = mutate_for_zones(mutant, statuses_available, zone_mutpb, rand)

Expand Down
3 changes: 2 additions & 1 deletion sampo/scheduler/genetic/schedule_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def create_toolbox(wg: WorkGraph,

worker_pool, index2node, index2zone, work_id2index, worker_name2index, index2contractor_obj, \
worker_pool_indices, contractor2index, contractor_borders, node_indices, priorities, parents, children, \
resources_border = prepare_optimized_data_structures(wg, contractors, landscape)
resources_border, contractors_available = prepare_optimized_data_structures(wg, contractors, landscape, spec)

init_chromosomes: dict[str, tuple[ChromosomeType, float, ScheduleSpec]] = \
{name: (convert_schedule_to_chromosome(work_id2index, worker_name2index,
Expand Down Expand Up @@ -79,6 +79,7 @@ def create_toolbox(wg: WorkGraph,
parents,
children,
resources_border,
contractors_available,
assigned_parent_time,
fitness_weights,
work_estimator,
Expand Down
Loading