From 78debc1dc9270a67db5adfb186a7a9f7f13355d0 Mon Sep 17 00:00:00 2001 From: Stanislav <39829332+StannisMod@users.noreply.github.com> Date: Mon, 13 May 2024 18:03:53 +0300 Subject: [PATCH] The first hybridisation (#83) Extended all schedulers with the ability of participate in population-based hybridisation schemes. Implemented cyclic hybridisation scheme, made experiments. --- experiments/hybridisation.ipynb | 190 ++++++++++++++++++++ experiments/hybridisation.py | 65 +++++++ experiments/hybridisation_results_proc.py | 20 +++ experiments/wg_generate.py | 14 ++ sampo/backend/__init__.py | 34 ++-- sampo/backend/default.py | 37 ++-- sampo/backend/multiproc.py | 37 ++-- sampo/hybrid/__init__.py | 0 sampo/hybrid/cycle.py | 71 ++++++++ sampo/hybrid/population.py | 54 ++++++ sampo/scheduler/base.py | 3 + sampo/scheduler/genetic/base.py | 41 ++++- sampo/scheduler/genetic/schedule_builder.py | 61 +++++-- 13 files changed, 566 insertions(+), 61 deletions(-) create mode 100644 experiments/hybridisation.ipynb create mode 100644 experiments/hybridisation.py create mode 100644 experiments/hybridisation_results_proc.py create mode 100644 experiments/wg_generate.py create mode 100644 sampo/hybrid/__init__.py create mode 100644 sampo/hybrid/cycle.py create mode 100644 sampo/hybrid/population.py diff --git a/experiments/hybridisation.ipynb b/experiments/hybridisation.ipynb new file mode 100644 index 00000000..9e29282d --- /dev/null +++ b/experiments/hybridisation.ipynb @@ -0,0 +1,190 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2024-04-06T14:56:18.492056800Z", + "start_time": "2024-04-06T14:56:16.553635700Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\Quarter\\PycharmProjects\\sampo\\venv\\lib\\site-packages\\scipy\\__init__.py:169: UserWarning: A NumPy version >=1.18.5 and <1.26.0 is required for this version of SciPy (detected version 1.26.4\n", + " warnings.warn(f\"A NumPy version >={np_minversion} and <{np_maxversion}\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Can not find native module; switching to default\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[matplotlib] [DEBUG] matplotlib data path: C:\\Users\\Quarter\\PycharmProjects\\sampo\\venv\\lib\\site-packages\\matplotlib\\mpl-data\n", + "[matplotlib] [DEBUG] CONFIGDIR=C:\\Users\\Quarter\\.matplotlib\n", + "[matplotlib] [DEBUG] interactive is False\n", + "[matplotlib] [DEBUG] platform is win32\n", + "[matplotlib] [DEBUG] CACHEDIR=C:\\Users\\Quarter\\.matplotlib\n", + "[matplotlib.font_manager] [DEBUG] Using fontManager instance from C:\\Users\\Quarter\\.matplotlib\\fontlist-v330.json\n" + ] + } + ], + "source": [ + "import sampo.scheduler\n", + "from sampo.hybrid.population_tabu import TabuPopulationScheduler\n", + "\n", + "from sampo.hybrid.cycle import CycleHybridScheduler\n", + "from sampo.api.genetic_api import ScheduleGenerationScheme\n", + "from sampo.scheduler import HEFTScheduler, HEFTBetweenScheduler, TopologicalScheduler, GeneticScheduler\n", + "from sampo.hybrid.population import HeuristicPopulationScheduler, GeneticPopulationScheduler\n", + "\n", + "heuristics = HeuristicPopulationScheduler([HEFTScheduler(), HEFTBetweenScheduler(), TopologicalScheduler()])\n", + "genetic1 = TabuPopulationScheduler()\n", + "genetic2 = GeneticPopulationScheduler(GeneticScheduler(sgs_type=ScheduleGenerationScheme.Parallel))\n", + "\n", + "hybrid = CycleHybridScheduler(heuristics, [genetic1, genetic2])" + ] + }, + { + "cell_type": "code", + "outputs": [], + "source": [ + "from sampo.generator.environment import get_contractor_by_wg\n", + "from sampo.generator import SimpleSynthetic\n", + "\n", + "wg = SimpleSynthetic(rand=231).work_graph(bottom_border=50)\n", + "contractors = [get_contractor_by_wg(wg)]" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-04-06T14:56:18.508087600Z", + "start_time": "2024-04-06T14:56:18.494058200Z" + } + }, + "id": "ca16a12c3f9bab5d", + "execution_count": 2 + }, + { + "cell_type": "code", + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Genetic optimizing took 2.000570297241211 ms\n", + "[TABU] iteration 0\n", + "[TABU] iteration 1\n", + "[TABU] iteration 2\n" + ] + }, + { + "ename": "KeyboardInterrupt", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001B[1;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[1;31mKeyboardInterrupt\u001B[0m Traceback (most recent call last)", + "Cell \u001B[1;32mIn[3], line 6\u001B[0m\n\u001B[0;32m 3\u001B[0m SAMPO\u001B[38;5;241m.\u001B[39mbackend\u001B[38;5;241m.\u001B[39mcache_scheduler_info(wg, contractors)\n\u001B[0;32m 4\u001B[0m SAMPO\u001B[38;5;241m.\u001B[39mbackend\u001B[38;5;241m.\u001B[39mcache_genetic_info()\n\u001B[1;32m----> 6\u001B[0m schedule \u001B[38;5;241m=\u001B[39m \u001B[43mhybrid\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mschedule\u001B[49m\u001B[43m(\u001B[49m\u001B[43mwg\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mcontractors\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[1;32m~\\PycharmProjects\\sampo\\sampo\\hybrid\\cycle.py:76\u001B[0m, in \u001B[0;36mCycleHybridScheduler.schedule\u001B[1;34m(self, wg, contractors, spec, assigned_parent_time, sgs_type, landscape)\u001B[0m\n\u001B[0;32m 73\u001B[0m cur_fitness \u001B[38;5;241m=\u001B[39m pop_fitness\n\u001B[0;32m 75\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m scheduler \u001B[38;5;129;01min\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_cycle_schedulers:\n\u001B[1;32m---> 76\u001B[0m pop \u001B[38;5;241m=\u001B[39m \u001B[43mscheduler\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mschedule\u001B[49m\u001B[43m(\u001B[49m\u001B[43mpop\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mwg\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mcontractors\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mspec\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43massigned_parent_time\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mlandscape\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 78\u001B[0m best_ind \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_get_best_individual(pop)\n\u001B[0;32m 80\u001B[0m toolbox \u001B[38;5;241m=\u001B[39m create_toolbox(wg\u001B[38;5;241m=\u001B[39mwg, contractors\u001B[38;5;241m=\u001B[39mcontractors, landscape\u001B[38;5;241m=\u001B[39mlandscape,\n\u001B[0;32m 81\u001B[0m assigned_parent_time\u001B[38;5;241m=\u001B[39massigned_parent_time, spec\u001B[38;5;241m=\u001B[39mspec,\n\u001B[0;32m 82\u001B[0m sgs_type\u001B[38;5;241m=\u001B[39msgs_type)\n", + "File \u001B[1;32m~\\PycharmProjects\\sampo\\sampo\\hybrid\\population_tabu.py:46\u001B[0m, in \u001B[0;36mTabuPopulationScheduler.schedule\u001B[1;34m(self, initial_population, wg, contractors, spec, assigned_parent_time, landscape)\u001B[0m\n\u001B[0;32m 41\u001B[0m tabu_leader \u001B[38;5;241m=\u001B[39m ChromosomeRW\u001B[38;5;241m.\u001B[39mfrom_sampo_chromosome(leader)\n\u001B[0;32m 43\u001B[0m opt_ord, opt_res \u001B[38;5;241m=\u001B[39m get_optimiser(tabu_toolbox,\n\u001B[0;32m 44\u001B[0m use_vp\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mTrue\u001B[39;00m,\n\u001B[0;32m 45\u001B[0m optimisers_lifetime\u001B[38;5;241m=\u001B[39mOptimiserLifetime\u001B[38;5;241m.\u001B[39mShort)\n\u001B[1;32m---> 46\u001B[0m tabu_leader \u001B[38;5;241m=\u001B[39m \u001B[43mopt_ord\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43moptimize\u001B[49m\u001B[43m(\u001B[49m\u001B[43mtabu_leader\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 47\u001B[0m tabu_leader \u001B[38;5;241m=\u001B[39m opt_res\u001B[38;5;241m.\u001B[39moptimize(tabu_leader)\n\u001B[0;32m 49\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m initial_population \u001B[38;5;241m+\u001B[39m tabu_leader\u001B[38;5;241m.\u001B[39mto_sampo_chromosome()\n", + "File \u001B[1;32m~\\PycharmProjects\\sampo\\tabusearch\\tabu_search.py:112\u001B[0m, in \u001B[0;36mTabuSearch.optimize\u001B[1;34m(self, x0)\u001B[0m\n\u001B[0;32m 109\u001B[0m history\u001B[38;5;241m.\u001B[39mappend(copy(x))\n\u001B[0;32m 111\u001B[0m \u001B[38;5;28;01mwhile\u001B[39;00m \u001B[38;5;28;01mTrue\u001B[39;00m:\n\u001B[1;32m--> 112\u001B[0m neighbours \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mget_neighbours\u001B[49m\u001B[43m(\u001B[49m\u001B[43mx\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 113\u001B[0m choice \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mchoose(neighbours)\n\u001B[0;32m 114\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m choice \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n", + "File \u001B[1;32m~\\PycharmProjects\\sampo\\tabusearch\\tabu_search.py:128\u001B[0m, in \u001B[0;36mTabuSearch.get_neighbours\u001B[1;34m(self, x)\u001B[0m\n\u001B[0;32m 127\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mget_neighbours\u001B[39m(\u001B[38;5;28mself\u001B[39m, x: Solution) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m Iterable[Solution]:\n\u001B[1;32m--> 128\u001B[0m generated \u001B[38;5;241m=\u001B[39m [(behaviour\u001B[38;5;241m.\u001B[39mmutation_type, behaviour\u001B[38;5;241m.\u001B[39mmutate(x)) \u001B[38;5;28;01mfor\u001B[39;00m behaviour \u001B[38;5;129;01min\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mmutation_behaviour]\n\u001B[0;32m 129\u001B[0m solutions \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39msolution_factory(generated)\n\u001B[0;32m 130\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mfiltering_memory_criterion\u001B[38;5;241m.\u001B[39mfilter(solutions)\n", + "File \u001B[1;32m~\\PycharmProjects\\sampo\\tabusearch\\tabu_search.py:128\u001B[0m, in \u001B[0;36m\u001B[1;34m(.0)\u001B[0m\n\u001B[0;32m 127\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mget_neighbours\u001B[39m(\u001B[38;5;28mself\u001B[39m, x: Solution) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m Iterable[Solution]:\n\u001B[1;32m--> 128\u001B[0m generated \u001B[38;5;241m=\u001B[39m [(behaviour\u001B[38;5;241m.\u001B[39mmutation_type, \u001B[43mbehaviour\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmutate\u001B[49m\u001B[43m(\u001B[49m\u001B[43mx\u001B[49m\u001B[43m)\u001B[49m) \u001B[38;5;28;01mfor\u001B[39;00m behaviour \u001B[38;5;129;01min\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mmutation_behaviour]\n\u001B[0;32m 129\u001B[0m solutions \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39msolution_factory(generated)\n\u001B[0;32m 130\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mfiltering_memory_criterion\u001B[38;5;241m.\u001B[39mfilter(solutions)\n", + "File \u001B[1;32m~\\PycharmProjects\\sampo\\tabusearch\\mutation\\base.py:22\u001B[0m, in \u001B[0;36mMutationBehaviour.mutate\u001B[1;34m(self, pivot)\u001B[0m\n\u001B[0;32m 15\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[0;32m 16\u001B[0m \u001B[38;5;124;03mMain interface for generation of new solution space.\u001B[39;00m\n\u001B[0;32m 17\u001B[0m \u001B[38;5;124;03m:param pivot: Previous solution, whom neighbourhood should be found.\u001B[39;00m\n\u001B[0;32m 18\u001B[0m \u001B[38;5;124;03m:return: New solution space.\u001B[39;00m\n\u001B[0;32m 19\u001B[0m \u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[0;32m 20\u001B[0m \u001B[38;5;66;03m# TODO: erase\u001B[39;00m\n\u001B[0;32m 21\u001B[0m \u001B[38;5;66;03m# return [self._solution_factory(*mutation) for mutation in self._generate_mutations(pivot.position)]\u001B[39;00m\n\u001B[1;32m---> 22\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_generate_mutations\u001B[49m\u001B[43m(\u001B[49m\u001B[43mpivot\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mposition\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[1;32m~\\PycharmProjects\\sampo\\tabusearch\\experiments\\scheduling\\order_neighbourhood.py:73\u001B[0m, in \u001B[0;36mvariable_partitioning_order_shuffle\u001B[1;34m(ind, is_order_correct, distances, one_distance_trials, rng)\u001B[0m\n\u001B[0;32m 69\u001B[0m shuffled[trial_start:trial_start \u001B[38;5;241m+\u001B[39m distance] \\\n\u001B[0;32m 70\u001B[0m \u001B[38;5;241m=\u001B[39m shuffled[np_shuffled(np\u001B[38;5;241m.\u001B[39marange(trial_start, trial_start \u001B[38;5;241m+\u001B[39m distance))]\n\u001B[0;32m 72\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m is_order_correct(shuffled):\n\u001B[1;32m---> 73\u001B[0m result\u001B[38;5;241m.\u001B[39mappend((\u001B[43mind\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mcopy\u001B[49m\u001B[43m(\u001B[49m\u001B[43mactivity_list\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mshuffled\u001B[49m\u001B[43m)\u001B[49m, \u001B[38;5;124mf\u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;132;01m{\u001B[39;00mtrial_start\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m:\u001B[39m\u001B[38;5;132;01m{\u001B[39;00mdistance\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m'\u001B[39m))\n\u001B[0;32m 75\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m result\n", + "File \u001B[1;32m~\\PycharmProjects\\sampo\\tabusearch\\utility\\chromosome.py:112\u001B[0m, in \u001B[0;36mChromosomeRW.copy\u001B[1;34m(self, **kwargs)\u001B[0m\n\u001B[0;32m 102\u001B[0m new_chromosome \u001B[38;5;241m=\u001B[39m \\\n\u001B[0;32m 103\u001B[0m ChromosomeRW(\u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39m{\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mactivity_list\u001B[39m\u001B[38;5;124m'\u001B[39m: kwargs\u001B[38;5;241m.\u001B[39mget(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mactivity_list\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_activity_list\u001B[38;5;241m.\u001B[39mcopy()),\n\u001B[0;32m 104\u001B[0m \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mcontractor_resource_pools\u001B[39m\u001B[38;5;124m'\u001B[39m: kwargs\u001B[38;5;241m.\u001B[39mget(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mcontractor_resource_pools\u001B[39m\u001B[38;5;124m'\u001B[39m,\n\u001B[1;32m (...)\u001B[0m\n\u001B[0;32m 109\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_resource_activity_alloc\u001B[38;5;241m.\u001B[39mcopy()),\n\u001B[0;32m 110\u001B[0m \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mvalidate_sizes\u001B[39m\u001B[38;5;124m'\u001B[39m: kwargs\u001B[38;5;241m.\u001B[39mget(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mvalidate_sizes\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;28;01mFalse\u001B[39;00m)})\n\u001B[0;32m 111\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_rest_info:\n\u001B[1;32m--> 112\u001B[0m new_chromosome\u001B[38;5;241m.\u001B[39madd_rest_info(\u001B[38;5;241m*\u001B[39m\u001B[43mdeepcopy\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_rest_info\u001B[49m\u001B[43m)\u001B[49m)\n\u001B[0;32m 114\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m new_chromosome\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:146\u001B[0m, in \u001B[0;36mdeepcopy\u001B[1;34m(x, memo, _nil)\u001B[0m\n\u001B[0;32m 144\u001B[0m copier \u001B[38;5;241m=\u001B[39m _deepcopy_dispatch\u001B[38;5;241m.\u001B[39mget(\u001B[38;5;28mcls\u001B[39m)\n\u001B[0;32m 145\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m copier \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[1;32m--> 146\u001B[0m y \u001B[38;5;241m=\u001B[39m \u001B[43mcopier\u001B[49m\u001B[43m(\u001B[49m\u001B[43mx\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmemo\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 147\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m 148\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28missubclass\u001B[39m(\u001B[38;5;28mcls\u001B[39m, \u001B[38;5;28mtype\u001B[39m):\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:206\u001B[0m, in \u001B[0;36m_deepcopy_list\u001B[1;34m(x, memo, deepcopy)\u001B[0m\n\u001B[0;32m 204\u001B[0m append \u001B[38;5;241m=\u001B[39m y\u001B[38;5;241m.\u001B[39mappend\n\u001B[0;32m 205\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m a \u001B[38;5;129;01min\u001B[39;00m x:\n\u001B[1;32m--> 206\u001B[0m append(\u001B[43mdeepcopy\u001B[49m\u001B[43m(\u001B[49m\u001B[43ma\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmemo\u001B[49m\u001B[43m)\u001B[49m)\n\u001B[0;32m 207\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m y\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:172\u001B[0m, in \u001B[0;36mdeepcopy\u001B[1;34m(x, memo, _nil)\u001B[0m\n\u001B[0;32m 170\u001B[0m y \u001B[38;5;241m=\u001B[39m x\n\u001B[0;32m 171\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m--> 172\u001B[0m y \u001B[38;5;241m=\u001B[39m \u001B[43m_reconstruct\u001B[49m\u001B[43m(\u001B[49m\u001B[43mx\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmemo\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mrv\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 174\u001B[0m \u001B[38;5;66;03m# If is its own copy, don't memoize.\u001B[39;00m\n\u001B[0;32m 175\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m y \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m x:\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:271\u001B[0m, in \u001B[0;36m_reconstruct\u001B[1;34m(x, memo, func, args, state, listiter, dictiter, deepcopy)\u001B[0m\n\u001B[0;32m 269\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m state \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[0;32m 270\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m deep:\n\u001B[1;32m--> 271\u001B[0m state \u001B[38;5;241m=\u001B[39m \u001B[43mdeepcopy\u001B[49m\u001B[43m(\u001B[49m\u001B[43mstate\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmemo\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 272\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mhasattr\u001B[39m(y, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m__setstate__\u001B[39m\u001B[38;5;124m'\u001B[39m):\n\u001B[0;32m 273\u001B[0m y\u001B[38;5;241m.\u001B[39m__setstate__(state)\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:146\u001B[0m, in \u001B[0;36mdeepcopy\u001B[1;34m(x, memo, _nil)\u001B[0m\n\u001B[0;32m 144\u001B[0m copier \u001B[38;5;241m=\u001B[39m _deepcopy_dispatch\u001B[38;5;241m.\u001B[39mget(\u001B[38;5;28mcls\u001B[39m)\n\u001B[0;32m 145\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m copier \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[1;32m--> 146\u001B[0m y \u001B[38;5;241m=\u001B[39m \u001B[43mcopier\u001B[49m\u001B[43m(\u001B[49m\u001B[43mx\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmemo\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 147\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m 148\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28missubclass\u001B[39m(\u001B[38;5;28mcls\u001B[39m, \u001B[38;5;28mtype\u001B[39m):\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:231\u001B[0m, in \u001B[0;36m_deepcopy_dict\u001B[1;34m(x, memo, deepcopy)\u001B[0m\n\u001B[0;32m 229\u001B[0m memo[\u001B[38;5;28mid\u001B[39m(x)] \u001B[38;5;241m=\u001B[39m y\n\u001B[0;32m 230\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m key, value \u001B[38;5;129;01min\u001B[39;00m x\u001B[38;5;241m.\u001B[39mitems():\n\u001B[1;32m--> 231\u001B[0m y[deepcopy(key, memo)] \u001B[38;5;241m=\u001B[39m \u001B[43mdeepcopy\u001B[49m\u001B[43m(\u001B[49m\u001B[43mvalue\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmemo\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 232\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m y\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:172\u001B[0m, in \u001B[0;36mdeepcopy\u001B[1;34m(x, memo, _nil)\u001B[0m\n\u001B[0;32m 170\u001B[0m y \u001B[38;5;241m=\u001B[39m x\n\u001B[0;32m 171\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m--> 172\u001B[0m y \u001B[38;5;241m=\u001B[39m \u001B[43m_reconstruct\u001B[49m\u001B[43m(\u001B[49m\u001B[43mx\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmemo\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mrv\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 174\u001B[0m \u001B[38;5;66;03m# If is its own copy, don't memoize.\u001B[39;00m\n\u001B[0;32m 175\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m y \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m x:\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:297\u001B[0m, in \u001B[0;36m_reconstruct\u001B[1;34m(x, memo, func, args, state, listiter, dictiter, deepcopy)\u001B[0m\n\u001B[0;32m 295\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m key, value \u001B[38;5;129;01min\u001B[39;00m dictiter:\n\u001B[0;32m 296\u001B[0m key \u001B[38;5;241m=\u001B[39m deepcopy(key, memo)\n\u001B[1;32m--> 297\u001B[0m value \u001B[38;5;241m=\u001B[39m \u001B[43mdeepcopy\u001B[49m\u001B[43m(\u001B[49m\u001B[43mvalue\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmemo\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 298\u001B[0m y[key] \u001B[38;5;241m=\u001B[39m value\n\u001B[0;32m 299\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:172\u001B[0m, in \u001B[0;36mdeepcopy\u001B[1;34m(x, memo, _nil)\u001B[0m\n\u001B[0;32m 170\u001B[0m y \u001B[38;5;241m=\u001B[39m x\n\u001B[0;32m 171\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m--> 172\u001B[0m y \u001B[38;5;241m=\u001B[39m \u001B[43m_reconstruct\u001B[49m\u001B[43m(\u001B[49m\u001B[43mx\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmemo\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mrv\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 174\u001B[0m \u001B[38;5;66;03m# If is its own copy, don't memoize.\u001B[39;00m\n\u001B[0;32m 175\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m y \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m x:\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:271\u001B[0m, in \u001B[0;36m_reconstruct\u001B[1;34m(x, memo, func, args, state, listiter, dictiter, deepcopy)\u001B[0m\n\u001B[0;32m 269\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m state \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[0;32m 270\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m deep:\n\u001B[1;32m--> 271\u001B[0m state \u001B[38;5;241m=\u001B[39m \u001B[43mdeepcopy\u001B[49m\u001B[43m(\u001B[49m\u001B[43mstate\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmemo\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 272\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mhasattr\u001B[39m(y, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m__setstate__\u001B[39m\u001B[38;5;124m'\u001B[39m):\n\u001B[0;32m 273\u001B[0m y\u001B[38;5;241m.\u001B[39m__setstate__(state)\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:146\u001B[0m, in \u001B[0;36mdeepcopy\u001B[1;34m(x, memo, _nil)\u001B[0m\n\u001B[0;32m 144\u001B[0m copier \u001B[38;5;241m=\u001B[39m _deepcopy_dispatch\u001B[38;5;241m.\u001B[39mget(\u001B[38;5;28mcls\u001B[39m)\n\u001B[0;32m 145\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m copier \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[1;32m--> 146\u001B[0m y \u001B[38;5;241m=\u001B[39m \u001B[43mcopier\u001B[49m\u001B[43m(\u001B[49m\u001B[43mx\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmemo\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 147\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m 148\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28missubclass\u001B[39m(\u001B[38;5;28mcls\u001B[39m, \u001B[38;5;28mtype\u001B[39m):\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:231\u001B[0m, in \u001B[0;36m_deepcopy_dict\u001B[1;34m(x, memo, deepcopy)\u001B[0m\n\u001B[0;32m 229\u001B[0m memo[\u001B[38;5;28mid\u001B[39m(x)] \u001B[38;5;241m=\u001B[39m y\n\u001B[0;32m 230\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m key, value \u001B[38;5;129;01min\u001B[39;00m x\u001B[38;5;241m.\u001B[39mitems():\n\u001B[1;32m--> 231\u001B[0m y[deepcopy(key, memo)] \u001B[38;5;241m=\u001B[39m \u001B[43mdeepcopy\u001B[49m\u001B[43m(\u001B[49m\u001B[43mvalue\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mmemo\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m 232\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m y\n", + "File \u001B[1;32m~\\AppData\\Local\\Programs\\Python\\Python310\\lib\\copy.py:128\u001B[0m, in \u001B[0;36mdeepcopy\u001B[1;34m(x, memo, _nil)\u001B[0m\n\u001B[0;32m 124\u001B[0m d[PyStringMap] \u001B[38;5;241m=\u001B[39m PyStringMap\u001B[38;5;241m.\u001B[39mcopy\n\u001B[0;32m 126\u001B[0m \u001B[38;5;28;01mdel\u001B[39;00m d, t\n\u001B[1;32m--> 128\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mdeepcopy\u001B[39m(x, memo\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m, _nil\u001B[38;5;241m=\u001B[39m[]):\n\u001B[0;32m 129\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Deep copy operation on arbitrary Python objects.\u001B[39;00m\n\u001B[0;32m 130\u001B[0m \n\u001B[0;32m 131\u001B[0m \u001B[38;5;124;03m See the module's __doc__ string for more info.\u001B[39;00m\n\u001B[0;32m 132\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[0;32m 134\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m memo \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n", + "\u001B[1;31mKeyboardInterrupt\u001B[0m: " + ] + } + ], + "source": [ + "from sampo.base import SAMPO\n", + "\n", + "SAMPO.backend.cache_scheduler_info(wg, contractors)\n", + "SAMPO.backend.cache_genetic_info()\n", + "\n", + "schedule = hybrid.schedule(wg, contractors)" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-04-06T15:17:29.179438900Z", + "start_time": "2024-04-06T14:56:19.246095400Z" + } + }, + "id": "f6660d32cf8ab626", + "execution_count": 3 + }, + { + "cell_type": "code", + "outputs": [], + "source": [ + "schedule.execution_time" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-04-06T14:02:48.788028300Z", + "start_time": "2024-04-06T14:02:48.783027500Z" + } + }, + "id": "b1b74a82acd50d54", + "execution_count": null + }, + { + "cell_type": "code", + "outputs": [], + "source": [], + "metadata": { + "collapsed": false + }, + "id": "5706ce1bc0b7fd04" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/experiments/hybridisation.py b/experiments/hybridisation.py new file mode 100644 index 00000000..dcb53d13 --- /dev/null +++ b/experiments/hybridisation.py @@ -0,0 +1,65 @@ +import json + +import pathos +from tqdm import tqdm + +import sampo.scheduler +from sampo.backend.multiproc import MultiprocessingComputationalBackend + +from sampo.hybrid.population_tabu import TabuPopulationScheduler + +from sampo.hybrid.cycle import CycleHybridScheduler +from sampo.api.genetic_api import ScheduleGenerationScheme +from sampo.scheduler import HEFTScheduler, HEFTBetweenScheduler, TopologicalScheduler, GeneticScheduler +from sampo.hybrid.population import HeuristicPopulationScheduler, GeneticPopulationScheduler + +from sampo.generator.environment import get_contractor_by_wg +from sampo.generator import SimpleSynthetic + +from sampo.base import SAMPO +from sampo.schemas import WorkGraph + +def run_experiment(args): + graph_size, iteration = args + + heuristics = HeuristicPopulationScheduler([HEFTScheduler(), HEFTBetweenScheduler(), TopologicalScheduler()]) + # genetic1 = TabuPopulationScheduler() + genetic1 = GeneticPopulationScheduler(GeneticScheduler(mutate_order=0.2, + mutate_resources=0.2, + sgs_type=ScheduleGenerationScheme.Parallel)) + genetic2 = GeneticPopulationScheduler(GeneticScheduler(mutate_order=0.001, + mutate_resources=0.001, + sgs_type=ScheduleGenerationScheme.Parallel)) + + hybrid_combine = CycleHybridScheduler(heuristics, [genetic1, genetic2], max_plateau_size=1) + hybrid_genetic1 = CycleHybridScheduler(heuristics, [genetic1], max_plateau_size=1) + hybrid_genetic2 = CycleHybridScheduler(heuristics, [genetic2], max_plateau_size=1) + + wg = WorkGraph.load('wgs', f'{graph_size}_{iteration}') + contractors = [get_contractor_by_wg(wg)] + + # SAMPO.backend = MultiprocessingComputationalBackend(n_cpus=10) + SAMPO.backend.cache_scheduler_info(wg, contractors) + SAMPO.backend.cache_genetic_info() + + schedule_hybrid_combine = hybrid_combine.schedule(wg, contractors) + schedule_genetic1 = hybrid_genetic1.schedule(wg, contractors) + schedule_genetic2 = hybrid_genetic2.schedule(wg, contractors) + + # print(f'Hybrid combine: {schedule_hybrid_combine.execution_time}') + # print(f'Scheduler 1 cycled: {schedule_genetic1.execution_time}') + # print(f'Scheduler 2 cycled: {schedule_genetic2.execution_time}') + return schedule_hybrid_combine.execution_time, schedule_genetic1.execution_time, schedule_genetic2.execution_time + +if __name__ == '__main__': + arguments = [(graph_size, iteration) for graph_size in [100, 200, 300, 400, 500] for iteration in range(5)] + results = {graph_size: [] for graph_size in [100, 200, 300, 400, 500]} + + with pathos.multiprocessing.Pool(processes=11) as p: + r = p.map(run_experiment, arguments) + + for (graph_size, _), (combined_time, time1, time2) in zip(arguments, r): + results[graph_size].append((combined_time / time1, combined_time / time2)) + + with open('hybrid_results.json', 'w') as f: + json.dump(results, f) diff --git a/experiments/hybridisation_results_proc.py b/experiments/hybridisation_results_proc.py new file mode 100644 index 00000000..4fdf5d71 --- /dev/null +++ b/experiments/hybridisation_results_proc.py @@ -0,0 +1,20 @@ +import json +from itertools import chain +from random import Random + +import matplotlib.pyplot as plt + +with open('hybrid_results.json', 'r') as f: + results = json.load(f) + +results = {int(graph_size): [100 * (1 - res) for res in chain(*result)] + for graph_size, result in results.items()} +graph_sizes = results.keys() + +rand = Random() + +plt.title('Прирост качества планов\nот применения гибридизации', fontsize=16) +plt.xlabel('Размер графа') +plt.ylabel('% прироста качества относительно базового алгоритма') +plt.boxplot(results.values(), labels=graph_sizes) +plt.show() diff --git a/experiments/wg_generate.py b/experiments/wg_generate.py new file mode 100644 index 00000000..a35a3ac4 --- /dev/null +++ b/experiments/wg_generate.py @@ -0,0 +1,14 @@ +from sampo.generator import SimpleSynthetic + +from tqdm import tqdm + +ss = SimpleSynthetic(rand=231) + +for size in range(100, 500 + 1, 100): + for i in tqdm(range(100)): + wg = ss.work_graph(bottom_border=size - 5, + top_border=size) + while not (size - 5 <= wg.vertex_count <= size): + wg = ss.work_graph(bottom_border=size - 20, + top_border=size) + wg.dump('wgs', f'{size}_{i}') diff --git a/sampo/backend/__init__.py b/sampo/backend/__init__.py index 9062abf3..8fcd40d2 100644 --- a/sampo/backend/__init__.py +++ b/sampo/backend/__init__.py @@ -7,6 +7,7 @@ from sampo.api.genetic_api import ChromosomeType, FitnessFunction, Individual, ScheduleGenerationScheme from sampo.schemas import WorkGraph, Contractor, LandscapeConfiguration, Schedule, GraphNode, Time, WorkTimeEstimator from sampo.schemas.schedule_spec import ScheduleSpec +from sampo.schemas.time_estimator import DefaultWorkEstimator T = TypeVar('T') R = TypeVar('R') @@ -20,7 +21,7 @@ def __init__(self): self._landscape = None self._spec = None self._rand = Random() - self._work_estimator = None + self._work_estimator = DefaultWorkEstimator() # additional genetic parameters self._toolbox = None @@ -41,26 +42,27 @@ def __init__(self): def cache_scheduler_info(self, wg: WorkGraph, contractors: list[Contractor], - landscape: LandscapeConfiguration, - spec: ScheduleSpec, + landscape: LandscapeConfiguration = LandscapeConfiguration(), + spec: ScheduleSpec = ScheduleSpec(), rand: Random | None = None, - work_estimator: WorkTimeEstimator | None = None): + work_estimator: WorkTimeEstimator = DefaultWorkEstimator()): ... @abstractmethod def cache_genetic_info(self, - population_size: int, - mutate_order: float, - mutate_resources: float, - mutate_zones: float, - deadline: Time | None, - weights: list[int] | None, - init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, float]], - assigned_parent_time: Time, - fitness_weights: tuple[int | float, ...], - sgs_type: ScheduleGenerationScheme, - only_lft_initialization: bool, - is_multiobjective: bool): + population_size: int = 50, + mutate_order: float = 0.1, + mutate_resources: float = 0.05, + mutate_zones: float = 0.05, + deadline: Time | None = None, + weights: list[int] | None = None, + init_schedules: dict[ + str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, float]] = None, + assigned_parent_time: Time = Time(0), + fitness_weights: tuple[int | float, ...] = None, + sgs_type: ScheduleGenerationScheme = ScheduleGenerationScheme.Parallel, + only_lft_initialization: bool = False, + is_multiobjective: bool = False): ... @abstractmethod diff --git a/sampo/backend/default.py b/sampo/backend/default.py index fd9fc946..96203e77 100644 --- a/sampo/backend/default.py +++ b/sampo/backend/default.py @@ -18,10 +18,10 @@ def map(self, action: Callable[[T], R], values: list[T]) -> list[R]: def cache_scheduler_info(self, wg: WorkGraph, contractors: list[Contractor], - landscape: LandscapeConfiguration, - spec: ScheduleSpec, + landscape: LandscapeConfiguration = LandscapeConfiguration(), + spec: ScheduleSpec = ScheduleSpec(), rand: Random | None = None, - work_estimator: WorkTimeEstimator | None = None): + work_estimator: WorkTimeEstimator = DefaultWorkEstimator()): self._wg = wg self._contractors = contractors self._landscape = landscape @@ -31,18 +31,18 @@ def cache_scheduler_info(self, self._toolbox = None def cache_genetic_info(self, - population_size: int, - mutate_order: float, - mutate_resources: float, - mutate_zones: float, - deadline: Time | None, - weights: list[int] | None, - init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, float]], - assigned_parent_time: Time, - fitness_weights: tuple[int | float, ...], - sgs_type: ScheduleGenerationScheme, - only_lft_initialization: bool, - is_multiobjective: bool): + population_size: int = 50, + mutate_order: float = 0.1, + mutate_resources: float = 0.05, + mutate_zones: float = 0.05, + deadline: Time | None = None, + weights: list[int] | None = None, + init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, float]] = None, + assigned_parent_time: Time = Time(0), + fitness_weights: tuple[int | float, ...] = None, + sgs_type: ScheduleGenerationScheme = ScheduleGenerationScheme.Parallel, + only_lft_initialization: bool = False, + is_multiobjective: bool = False): self._selection_size = population_size self._mutate_order = mutate_order self._mutate_resources = mutate_resources @@ -61,8 +61,11 @@ def _ensure_toolbox_created(self): if self._toolbox is None: from sampo.scheduler.genetic.utils import init_chromosomes_f, create_toolbox_using_cached_chromosomes - init_chromosomes = init_chromosomes_f(self._wg, self._contractors, self._init_schedules, - self._landscape) + if self._init_schedules: + init_chromosomes = init_chromosomes_f(self._wg, self._contractors, self._init_schedules, + self._landscape) + else: + init_chromosomes = [] rand = self._rand or Random() work_estimator = self._work_estimator or DefaultWorkEstimator() diff --git a/sampo/backend/multiproc.py b/sampo/backend/multiproc.py index 05ea10ef..944e0628 100644 --- a/sampo/backend/multiproc.py +++ b/sampo/backend/multiproc.py @@ -115,30 +115,35 @@ def _ensure_pool_created(self): def cache_scheduler_info(self, wg: WorkGraph, contractors: list[Contractor], - landscape: LandscapeConfiguration, - spec: ScheduleSpec, + landscape: LandscapeConfiguration = LandscapeConfiguration(), + spec: ScheduleSpec = ScheduleSpec(), rand: Random | None = None, work_estimator: WorkTimeEstimator = DefaultWorkEstimator()): super().cache_scheduler_info(wg, contractors, landscape, spec, rand, work_estimator) self._pool = None def cache_genetic_info(self, - selection_size: int, - mutate_order: float, - mutate_resources: float, - mutate_zones: float, - deadline: Time | None, - weights: list[int] | None, - init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, float]], - assigned_parent_time: Time, - fitness_weights: tuple[int | float, ...], - sgs_type: ScheduleGenerationScheme, - only_lft_initialization: bool, - is_multiobjective: bool): - super().cache_genetic_info(selection_size, mutate_order, mutate_resources, mutate_zones, deadline, + population_size: int = 50, + mutate_order: float = 0.1, + mutate_resources: float = 0.05, + mutate_zones: float = 0.05, + deadline: Time | None = None, + weights: list[int] | None = None, + init_schedules: dict[ + str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, float]] = None, + assigned_parent_time: Time = Time(0), + fitness_weights: tuple[int | float, ...] = None, + sgs_type: ScheduleGenerationScheme = ScheduleGenerationScheme.Parallel, + only_lft_initialization: bool = False, + is_multiobjective: bool = False): + super().cache_genetic_info(population_size, mutate_order, mutate_resources, mutate_zones, deadline, weights, init_schedules, assigned_parent_time, fitness_weights, sgs_type, only_lft_initialization, is_multiobjective) - self._init_chromosomes = init_chromosomes_f(self._wg, self._contractors, init_schedules, self._landscape) + if init_schedules: + self._init_chromosomes = init_chromosomes_f(self._wg, self._contractors, init_schedules, + self._landscape) + else: + self._init_chromosomes = [] self._pool = None def compute_chromosomes(self, fitness: FitnessFunction, chromosomes: list[ChromosomeType]) -> list[float]: diff --git a/sampo/hybrid/__init__.py b/sampo/hybrid/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sampo/hybrid/cycle.py b/sampo/hybrid/cycle.py new file mode 100644 index 00000000..79e2204f --- /dev/null +++ b/sampo/hybrid/cycle.py @@ -0,0 +1,71 @@ +import numpy as np + +from sampo.api.genetic_api import FitnessFunction, ChromosomeType, ScheduleGenerationScheme +from sampo.base import SAMPO +from sampo.hybrid.population import PopulationScheduler +from sampo.scheduler.genetic import TimeFitness +from sampo.scheduler.genetic.schedule_builder import create_toolbox +from sampo.schemas import WorkGraph, Contractor, Time, LandscapeConfiguration, Schedule +from sampo.schemas.schedule_spec import ScheduleSpec + + +class CycleHybridScheduler: + def __init__(self, + starting_scheduler: PopulationScheduler, + cycle_schedulers: list[PopulationScheduler], + fitness: FitnessFunction = TimeFitness(), + max_plateau_size: int = 2): + self._starting_scheduler = starting_scheduler + self._cycle_schedulers = cycle_schedulers + self._fitness = fitness + self._max_plateau_size = max_plateau_size + + def _get_population_fitness(self, pop: list[ChromosomeType]): + # return best chromosome's fitness + return min(SAMPO.backend.compute_chromosomes(self._fitness, pop)) + + def _get_best_individual(self, pop: list[ChromosomeType]) -> ChromosomeType: + fitness = SAMPO.backend.compute_chromosomes(self._fitness, pop) + return pop[np.argmin(fitness)] + + def run(self, + wg: WorkGraph, + contractors: list[Contractor], + spec: ScheduleSpec = ScheduleSpec(), + assigned_parent_time: Time = Time(0), + landscape: LandscapeConfiguration = LandscapeConfiguration()) -> ChromosomeType: + pop = self._starting_scheduler.schedule([], wg, contractors, spec, assigned_parent_time, landscape) + + cur_fitness = Time.inf().value + plateau_steps = 0 + + while True: + pop_fitness = self._get_population_fitness(pop) + if pop_fitness == cur_fitness: + plateau_steps += 1 + if plateau_steps == self._max_plateau_size: + break + else: + plateau_steps = 0 + cur_fitness = pop_fitness + + for scheduler in self._cycle_schedulers: + pop = scheduler.schedule(pop, wg, contractors, spec, assigned_parent_time, landscape) + + return self._get_best_individual(pop) + + def schedule(self, + wg: WorkGraph, + contractors: list[Contractor], + spec: ScheduleSpec = ScheduleSpec(), + assigned_parent_time: Time = Time(0), + sgs_type: ScheduleGenerationScheme = ScheduleGenerationScheme.Parallel, + landscape: LandscapeConfiguration = LandscapeConfiguration()) -> Schedule: + best_ind = self.run(wg, contractors, spec, assigned_parent_time, landscape) + + toolbox = create_toolbox(wg=wg, contractors=contractors, landscape=landscape, + assigned_parent_time=assigned_parent_time, spec=spec, + sgs_type=sgs_type) + node2swork = toolbox.chromosome_to_schedule(best_ind)[0] + + return Schedule.from_scheduled_works(node2swork.values(), wg) diff --git a/sampo/hybrid/population.py b/sampo/hybrid/population.py new file mode 100644 index 00000000..0c635da7 --- /dev/null +++ b/sampo/hybrid/population.py @@ -0,0 +1,54 @@ +from abc import ABC, abstractmethod + +from sampo.api.genetic_api import ChromosomeType, Individual +from sampo.scheduler import GeneticScheduler, Scheduler +from sampo.scheduler.genetic.schedule_builder import create_toolbox +from sampo.schemas import WorkGraph, Contractor, Time, LandscapeConfiguration +from sampo.schemas.schedule_spec import ScheduleSpec + + +class PopulationScheduler(ABC): + + @abstractmethod + def schedule(self, + initial_population: list[ChromosomeType], + wg: WorkGraph, + contractors: list[Contractor], + spec: ScheduleSpec = ScheduleSpec(), + assigned_parent_time: Time = Time(0), + landscape: LandscapeConfiguration = LandscapeConfiguration()) -> list[ChromosomeType]: + ... + + +class GeneticPopulationScheduler(PopulationScheduler): + def __init__(self, genetic: GeneticScheduler): + self._genetic = genetic + + def schedule(self, + initial_population: list[ChromosomeType], + wg: WorkGraph, + contractors: list[Contractor], + spec: ScheduleSpec = ScheduleSpec(), + assigned_parent_time: Time = Time(0), + landscape: LandscapeConfiguration = LandscapeConfiguration()) -> list[ChromosomeType]: + return self._genetic.upgrade_pop(wg, contractors, initial_population, spec, + assigned_parent_time, landscape=landscape) + + +class HeuristicPopulationScheduler(PopulationScheduler): + def __init__(self, schedulers: list[Scheduler]): + self._schedulers = schedulers + + def schedule(self, + initial_population: list[ChromosomeType], + wg: WorkGraph, + contractors: list[Contractor], + spec: ScheduleSpec = ScheduleSpec(), + assigned_parent_time: Time = Time(0), + landscape: LandscapeConfiguration = LandscapeConfiguration()) -> list[ChromosomeType]: + toolbox = create_toolbox(wg=wg, contractors=contractors, + spec=spec, assigned_parent_time=assigned_parent_time, + landscape=landscape) + return [toolbox.Individual(toolbox.schedule_to_chromosome(schedule=schedule)) + for scheduler in self._schedulers + for schedule in scheduler.schedule(wg, contractors, spec, landscape=landscape)] diff --git a/sampo/scheduler/base.py b/sampo/scheduler/base.py index 2df6c0a4..a060fcf2 100644 --- a/sampo/scheduler/base.py +++ b/sampo/scheduler/base.py @@ -102,6 +102,9 @@ def optimize_resources_using_spec(work_unit: WorkUnit, worker_team: list[Worker] data in its closure and run optimization process when receives `optimize_array`. Passing None or default value means this function should only apply spec. """ + worker_reqs = set(wr.kind for wr in work_unit.worker_reqs) + worker_team = [worker for worker in worker_team if worker.name in worker_reqs] + if len(work_spec.assigned_workers) == len(work_unit.worker_reqs): # all resources passed in spec, skipping optimize_resources step for worker in worker_team: diff --git a/sampo/scheduler/genetic/base.py b/sampo/scheduler/genetic/base.py index ddb869b4..a4718c91 100644 --- a/sampo/scheduler/genetic/base.py +++ b/sampo/scheduler/genetic/base.py @@ -4,7 +4,7 @@ from sampo.api.genetic_api import ChromosomeType from sampo.scheduler.base import Scheduler, SchedulerType from sampo.scheduler.genetic.operators import FitnessFunction, TimeFitness -from sampo.scheduler.genetic.schedule_builder import build_schedules +from sampo.scheduler.genetic.schedule_builder import build_schedules, build_schedules_with_cache from sampo.scheduler.genetic.converter import ScheduleGenerationScheme from sampo.scheduler.heft.base import HEFTScheduler, HEFTBetweenScheduler from sampo.scheduler.lft.base import LFTScheduler @@ -201,6 +201,44 @@ def init_schedule(scheduler_class) -> tuple[Schedule | None, list[GraphNode] | N "87.5%": (*init_k_schedule(HEFTScheduler, 8 / 7), weights[6]) } + def upgrade_pop(self, + wg: WorkGraph, + contractors: list[Contractor], + pop: list[ChromosomeType], + spec: ScheduleSpec = ScheduleSpec(), + assigned_parent_time: Time = Time(0), + timeline: Timeline | None = None, + landscape: LandscapeConfiguration = LandscapeConfiguration()) -> list[ChromosomeType]: + mutate_order, mutate_resources, mutate_zones, size_of_population = self.get_params(wg.vertex_count) + deadline = None if self._optimize_resources else self._deadline + + _, new_pop = build_schedules_with_cache(wg, + contractors, + size_of_population, + self.number_of_generation, + mutate_order, + mutate_resources, + mutate_zones, + {}, + self.rand, + spec, + self._weights, + pop, + landscape, + self.fitness_constructor, + self.fitness_weights, + self.work_estimator, + self.sgs_type, + assigned_parent_time, + timeline, + self._time_border, + self._max_plateau_steps, + self._optimize_resources, + deadline, + self._only_lft_initialization, + self._is_multiobjective) + return new_pop + def schedule_with_cache(self, wg: WorkGraph, contractors: list[Contractor], @@ -240,6 +278,7 @@ def schedule_with_cache(self, self.rand, spec, self._weights, + None, landscape, self.fitness_constructor, self.fitness_weights, diff --git a/sampo/scheduler/genetic/schedule_builder.py b/sampo/scheduler/genetic/schedule_builder.py index 81ca6c5f..caada9eb 100644 --- a/sampo/scheduler/genetic/schedule_builder.py +++ b/sampo/scheduler/genetic/schedule_builder.py @@ -21,16 +21,16 @@ def create_toolbox(wg: WorkGraph, contractors: list[Contractor], - selection_size: int, - mutate_order: float, - mutate_resources: float, - mutate_zones: float, + selection_size: int = 50, + mutate_order: float = 0.05, + mutate_resources: float = 0.05, + mutate_zones: float = 0.05, init_schedules: dict[ - str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, float]], - rand: random.Random, + str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, float]] = {}, + rand: random.Random = random.Random(), spec: ScheduleSpec = ScheduleSpec(), fitness_weights: tuple[int | float, ...] = (-1,), - work_estimator: WorkTimeEstimator = None, + work_estimator: WorkTimeEstimator = DefaultWorkEstimator(), sgs_type: ScheduleGenerationScheme = ScheduleGenerationScheme.Parallel, assigned_parent_time: Time = Time(0), landscape: LandscapeConfiguration = LandscapeConfiguration(), @@ -97,6 +97,7 @@ def build_schedules(wg: WorkGraph, rand: random.Random, spec: ScheduleSpec, weights: list[int], + pop: list[ChromosomeType] = None, landscape: LandscapeConfiguration = LandscapeConfiguration(), fitness_object: FitnessFunction = TimeFitness(), fitness_weights: tuple[int | float, ...] = (-1,), @@ -111,6 +112,40 @@ def build_schedules(wg: WorkGraph, only_lft_initialization: bool = False, is_multiobjective: bool = False) \ -> 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] + + +def build_schedules_with_cache(wg: WorkGraph, + contractors: list[Contractor], + population_size: int, + generation_number: int, + mutpb_order: float, + mutpb_res: float, + mutpb_zones: float, + init_schedules: dict[str, tuple[Schedule, list[GraphNode] | None, ScheduleSpec, float]], + rand: random.Random, + spec: ScheduleSpec, + weights: list[int] = None, + pop: list[ChromosomeType] = None, + landscape: LandscapeConfiguration = LandscapeConfiguration(), + fitness_object: FitnessFunction = TimeFitness(), + fitness_weights: tuple[int | float, ...] = (-1,), + work_estimator: WorkTimeEstimator = DefaultWorkEstimator(), + sgs_type: ScheduleGenerationScheme = ScheduleGenerationScheme.Parallel, + assigned_parent_time: Time = Time(0), + timeline: Timeline | None = None, + time_border: int | None = None, + max_plateau_steps: int | None = None, + optimize_resources: bool = False, + deadline: Time | None = None, + only_lft_initialization: bool = False, + is_multiobjective: bool = False) \ + -> tuple[list[tuple[ScheduleWorkDict, Time, Timeline, list[GraphNode]]], list[ChromosomeType]]: """ Genetic algorithm. Structure of chromosome: @@ -141,15 +176,19 @@ def build_schedules(wg: WorkGraph, init_schedules, assigned_parent_time, fitness_weights, sgs_type, only_lft_initialization, is_multiobjective) - # create population of a given size - pop = SAMPO.backend.generate_first_population(population_size) - SAMPO.logger.info(f'Toolbox initialization & first population took {(time.time() - start) * 1000} ms') have_deadline = deadline is not None fitness_f = fitness_object if not have_deadline else TimeFitness() if have_deadline: toolbox.register_individual_constructor((-1,)) + + # create population of a given size + if pop is None: + pop = SAMPO.backend.generate_first_population(population_size) + else: + pop = [toolbox.Individual(chromosome) for chromosome in pop] + evaluation_start = time.time() hof = tools.ParetoFront(similar=compare_individuals) @@ -308,7 +347,7 @@ def build_schedules(wg: WorkGraph, schedule_start_time, timeline, order_nodes) for scheduled_works, schedule_start_time, timeline, order_nodes in best_schedules] - return best_schedules + return best_schedules, pop def compare_individuals(first: ChromosomeType, second: ChromosomeType) -> bool: