Skip to content
Open
242 changes: 242 additions & 0 deletions examples/fitness_history.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "6ad37843-2d46-41a7-8637-f7786eb7f72c",
"metadata": {},
"outputs": [],
"source": [
"from sampo.generator.base import SimpleSynthetic\n",
"from sampo.generator.environment.contractor_by_wg import get_contractor_by_wg\n",
"from sampo.scheduler.genetic.base import GeneticScheduler\n",
"from sampo.scheduler.genetic.operators import TimeAndResourcesFitness"
]
},
{
"cell_type": "markdown",
"id": "505ef085-4abb-48f5-9c22-cfc8211b8da0",
"metadata": {},
"source": [
"## Set parameters and generate synthetic graph"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af3dc563-fe56-4fea-96ef-30fb0f7552a2",
"metadata": {},
"outputs": [],
"source": [
"graph_size = 250\n",
"seed = 123\n",
"\n",
"size_of_population = 100\n",
"number_of_generation = 20\n",
"\n",
"mutate_order = 0.02\n",
"mutate_resources = 0.02\n",
"\n",
"fitness_constructor = TimeAndResourcesFitness()\n",
"fitness_weights = (-1, -1)\n",
"is_multiobjective = True\n",
"optimize_resources = True\n",
"\n",
"save_history_to = \"./history_test.json\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0b8c8da3-f19f-4c74-ac90-db27ef775be0",
"metadata": {},
"outputs": [],
"source": [
"ss = SimpleSynthetic(seed)\n",
"wg = ss.work_graph(bottom_border=graph_size)\n",
"contractors = [get_contractor_by_wg(wg)]\n",
"print(f\"Generated graph with size: {wg.vertex_count}\")"
]
},
{
"cell_type": "markdown",
"id": "d2add0c6-3b37-4a17-8af2-96340635b277",
"metadata": {},
"source": [
"## Use the genetic algorithm and save history"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "00a68d26-7d3b-4eb6-8395-247c0962c86d",
"metadata": {},
"outputs": [],
"source": [
"genetic_algorithm = GeneticScheduler(\n",
" number_of_generation=number_of_generation,\n",
" size_of_population=size_of_population,\n",
" \n",
" mutate_order=mutate_order,\n",
" mutate_resources=mutate_resources,\n",
" \n",
" fitness_constructor=fitness_constructor,\n",
" fitness_weights=fitness_weights,\n",
" is_multiobjective=is_multiobjective,\n",
" optimize_resources=optimize_resources,\n",
" \n",
" seed=seed,\n",
" save_history_to=save_history_to\n",
")\n",
"genetic_result = genetic_algorithm.schedule(wg, contractors)"
]
},
{
"cell_type": "markdown",
"id": "0a2019d6-dd49-4150-9c25-2cac2560063c",
"metadata": {},
"source": [
"## Get summary of the evolution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "73095ca6-9433-4270-abf5-f8beae4cb90d",
"metadata": {},
"outputs": [],
"source": [
"from sampo.scheduler.utils.fitness_history import FitnessHistorySummary\n",
"\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"import plotly.express as px\n",
"import plotly.io as pio\n",
"pio.templates.default = \"plotly_dark\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c6aefd90-e8ee-4ccb-9451-0ff5ba850792",
"metadata": {},
"outputs": [],
"source": [
"# Load history from file\n",
"summary = FitnessHistorySummary(save_history_to)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dadc10b3-9171-4b81-96cd-7faace7bd868",
"metadata": {},
"outputs": [],
"source": [
"population_means = np.array(summary.get_fitness_means())\n",
"\n",
"fig = px.line(x=population_means[:, 0], y=population_means[:, 1], markers=True)\n",
"fig.update_layout(height=700, width=700)\n",
"fig.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ea8885bf-def2-47a5-97e5-4cd176b7f4ee",
"metadata": {},
"outputs": [],
"source": [
"pareto_front_means = np.array(summary.get_fitness_means(only_pareto=True))\n",
"\n",
"fig = px.line(x=pareto_front_means[:, 0], y=pareto_front_means[:, 1], markers=True)\n",
"fig.update_layout(height=700, width=700)\n",
"fig.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "626ca043-54be-449d-a682-aef6abbad455",
"metadata": {},
"outputs": [],
"source": [
"pareto_ratios = summary.get_pareto_to_population_ratios()\n",
"\n",
"fig = px.line(y=pareto_ratios, markers=True)\n",
"fig.update_layout(height=500, width=1000)\n",
"fig.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a36746bb-867d-4f56-a691-ff373ed8e29c",
"metadata": {},
"outputs": [],
"source": [
"population_shifts = summary.get_generation_shifts()\n",
"pareto_front_shifts = summary.get_generation_shifts(only_pareto=True)\n",
"\n",
"fig = px.line(y=[population_shifts, pareto_front_shifts], markers=True)\n",
"fig.data[1].line.color = \"white\"\n",
"fig.update_layout(height=500, width=1000, showlegend=False)\n",
"fig.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "88d5aeb0-0af3-4002-9be4-6e090c6c0fd2",
"metadata": {},
"outputs": [],
"source": [
"population_uniqueness = summary.get_uniqueness_scores()\n",
"pareto_uniqueness = summary.get_uniqueness_scores(only_pareto=True)\n",
"\n",
"fig = px.line(y=[population_uniqueness, pareto_uniqueness], markers=True)\n",
"fig.data[1].line.color = \"white\"\n",
"fig.update_layout(height=500, width=1000, showlegend=False)\n",
"fig.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3564e9ca-a9fd-43df-9a80-cbd2c74426e4",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "f9bf8b8c-9ffd-4e0d-ae76-cdc4dbbf86c0",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.14.2"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
12 changes: 9 additions & 3 deletions sampo/scheduler/genetic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ def __init__(self,
# for optimization on one criteria set False
is_multiobjective: bool = False,
# for experiments with classic RCPSP formulation (initialize population with LFT)
only_lft_initialization: bool = False):
only_lft_initialization: bool = False,
# where to save history of fitness values
# saving instead of returning to avoid breaking modules
save_history_to: str | None = None):
super().__init__(scheduler_type=scheduler_type,
resource_optimizer=resource_optimizer,
work_estimator=work_estimator)
Expand All @@ -75,6 +78,7 @@ def __init__(self,
self._is_multiobjective = is_multiobjective
self._weights = weights
self._only_lft_initialization = only_lft_initialization
self.save_history_to = save_history_to

self._time_border = None
self._max_plateau_steps = None
Expand Down Expand Up @@ -248,7 +252,8 @@ def upgrade_pop(self,
self._optimize_resources,
deadline,
self._only_lft_initialization,
self._is_multiobjective)
self._is_multiobjective,
self.save_history_to)
return new_pop

def schedule_with_cache(self,
Expand Down Expand Up @@ -303,7 +308,8 @@ def schedule_with_cache(self,
self._optimize_resources,
deadline,
self._only_lft_initialization,
self._is_multiobjective)
self._is_multiobjective,
self.save_history_to)
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]
Expand Down
18 changes: 15 additions & 3 deletions sampo/scheduler/genetic/schedule_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from sampo.schemas.schedule_spec import ScheduleSpec
from sampo.schemas.time import Time
from sampo.schemas.time_estimator import WorkTimeEstimator, DefaultWorkEstimator
from sampo.scheduler.utils.fitness_history import FitnessHistory


def create_toolbox(wg: WorkGraph,
Expand Down Expand Up @@ -112,14 +113,16 @@ def build_schedules(wg: WorkGraph,
optimize_resources: bool = False,
deadline: Time | None = None,
only_lft_initialization: bool = False,
is_multiobjective: bool = False) \
is_multiobjective: bool = False,
save_history_to: str | None = None) \
-> list[tuple[ScheduleWorkDict, Time, Timeline, list[GraphNode]]]:
return build_schedules_with_cache(wg, contractors, population_size, generation_number,
mutpb_order, mutpb_res, mutpb_zones, init_schedules,
rand, spec, weights, pop, landscape, fitness_object,
fitness_weights, work_estimator, sgs_type, assigned_parent_time,
timeline, time_border, max_plateau_steps, optimize_resources,
deadline, only_lft_initialization, is_multiobjective)[0]
deadline, only_lft_initialization, is_multiobjective,
save_history_to)[0]


def build_schedules_with_cache(wg: WorkGraph,
Expand All @@ -146,7 +149,8 @@ def build_schedules_with_cache(wg: WorkGraph,
optimize_resources: bool = False,
deadline: Time | None = None,
only_lft_initialization: bool = False,
is_multiobjective: bool = False) \
is_multiobjective: bool = False,
save_history_to: str | None = None) \
-> tuple[list[tuple[ScheduleWorkDict, Time, Timeline, list[GraphNode]]], list[ChromosomeType]]:
"""
Genetic algorithm.
Expand Down Expand Up @@ -194,6 +198,7 @@ def build_schedules_with_cache(wg: WorkGraph,
evaluation_start = time.time()

hof = tools.ParetoFront(similar=compare_individuals)
fitness_history = FitnessHistory()

# map to each individual fitness function
fitness = SAMPO.backend.compute_chromosomes(fitness_f, pop)
Expand All @@ -204,6 +209,7 @@ def build_schedules_with_cache(wg: WorkGraph,
ind.fitness.values = fit

hof.update(pop)
fitness_history.update(pop, hof, pop, comment="first generation")
best_fitness = hof[0].fitness.values

SAMPO.logger.info(f'First population evaluation took {evaluation_time * 1000} ms')
Expand Down Expand Up @@ -236,6 +242,7 @@ def build_schedules_with_cache(wg: WorkGraph,
pop += offspring
pop = toolbox.select(pop)
hof.update(pop)
fitness_history.update(pop, hof, offspring, comment="genetic update")

prev_best_fitness = best_fitness
best_fitness = hof[0].fitness.values
Expand Down Expand Up @@ -283,6 +290,7 @@ def build_schedules_with_cache(wg: WorkGraph,
evaluation_time += time.time() - evaluation_start

hof.update(pop)
fitness_history.update(pop, hof, pop, comment="first deadline population")

if best_fitness[0] <= deadline:
# Optimizing resources
Expand Down Expand Up @@ -327,6 +335,7 @@ def build_schedules_with_cache(wg: WorkGraph,
pop += offspring
pop = toolbox.select(pop)
hof.update(pop)
fitness_history.update(pop, hof, offspring, comment="genetic deadline update")

prev_best_fitness = best_fitness
best_fitness = hof[0].fitness.values
Expand All @@ -338,6 +347,9 @@ def build_schedules_with_cache(wg: WorkGraph,
SAMPO.logger.info(f'Generations processing took {(time.time() - start) * 1000} ms')
SAMPO.logger.info(f'Full genetic processing took {(time.time() - global_start) * 1000} ms')
SAMPO.logger.info(f'Evaluation time: {evaluation_time * 1000}')
# save fitness history
if save_history_to:
fitness_history.save_to_json(path=save_history_to)

best_chromosomes = [chromosome for chromosome in hof]

Expand Down
Loading