{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# TP 2-3 : Branch-and-bound applied to a knapsack problem" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initialisation (à faire une seule fois)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "\u001b[32m\u001b[1m Updating\u001b[22m\u001b[39m registry at `~/.julia/registries/General`\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/.julia/environments/v1.6/Project.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/.julia/environments/v1.6/Manifest.toml`\n", "\u001b[32m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n", "\u001b[32m ✓ \u001b[39mTestOptinum\n", " 1 dependency successfully precompiled in 7 seconds (270 already precompiled)\n", "\u001b[32m\u001b[1m Resolving\u001b[22m\u001b[39m package versions...\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/.julia/environments/v1.6/Project.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/.julia/environments/v1.6/Manifest.toml`\n", "\u001b[32m\u001b[1mPrecompiling\u001b[22m\u001b[39m project...\n", "\u001b[32m ✓ \u001b[39mTestOptinum\n", " 1 dependency successfully precompiled in 4 seconds (270 already precompiled)\n" ] } ], "source": [ "import Pkg;\n", "Pkg.add(\"GraphRecipes\");\n", "Pkg.add(\"Plots\");\n", "using GraphRecipes, Plots #only used to visualize the search tree at the end of the branch-and-bound" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Récupération des données" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "readKnapInstance" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"\"\"Open and read a KnapFile.\n", "\n", "Args: \\\\\n", " - filename (String): the name of the file to read.\n", "\n", "Returns: \\\\\n", " - price (Vector{Integer}): prices of items to put in the KnapSack. \\\\\n", " - weight (Vector{Integer}): weights of items to put in the KnapSack. \\\\\n", " - capacity (Integer): the maximum capacity of the KnapSack.\n", "\"\"\"\n", "function readKnapInstance(filename)\n", " price = []\n", " weight = []\n", " capacity = -1\n", " open(filename) do f\n", " for i = 1:3\n", " tok = split(readline(f))\n", " if (tok[1] == \"ListPrices=\")\n", " for i = 2:(length(tok)-1)\n", " push!(price, parse(Int64, tok[i]))\n", " end\n", " elseif (tok[1] == \"ListWeights=\")\n", " for i = 2:(length(tok)-1)\n", " push!(weight, parse(Int64, tok[i]))\n", " end\n", " elseif (tok[1] == \"Capacity=\")\n", " capacity = parse(Int64, tok[2])\n", " else\n", " println(\"Unknown read :\", tok)\n", " end\n", " end\n", " end\n", " return price, weight, capacity\n", "end" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# on teste de lire tous les fichiers .opb\n", "for (root, dirs, files) in walkdir(\"data\")\n", " for file in files\n", " if endswith(file, \".opb\")\n", " readKnapInstance(root * \"/\" * file)\n", " end\n", " end\n", "end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Procédure d'application des tests de sondabilités TA, TO et TR pour le cas de la relaxation linéaire" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "TestsSondabilite_relaxlin" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"\"\"Test if a node should be pruned.\n", "\n", "Args: \\\\\n", " - x (Vector{Integer}): the node to be tested. \\\\\n", " - price (Vector{Integer}): prices of items to put in the KnapSack. \\\\\n", " - weight (Vector{Integer}): weights of items to put in the KnapSack. \\\\\n", " - capacity (Integer): the maximum capacity of the KnapSack. \\\\\n", " - BestProfit (Integer): the current BestProfit value. \\\\\n", " - Bestsol (Integer): the current BestSol values. \\\\\n", " - affich (Bool): determine if the function should print to stdout.\n", "\n", "Returns: \\\\\n", " - TA (Bool): true if the node is feasible. \\\\\n", " - TO (Bool): true if the node is optimal. \\\\\n", " - TR (Bool): true if the node is resolvable. \\\\\n", " - BestProfit (Integer): the updated value of BestProfit. \\\\\n", " - Bestsol (Vector{Integer}): the updated values of BestSol.\n", "\"\"\"\n", "function TestsSondabilite_relaxlin(x, price, weight, capacity, BestProfit, Bestsol, affich)\n", " TA, TO, TR = false, false, false\n", "\n", " if (!Constraints(x, weight, capacity)) # Test de faisabilite\n", " TA = true\n", " if affich\n", " println(\"TA\\n\")\n", " end\n", "\n", " elseif (Objective(x, price) <= BestProfit) # Test d'optimalite\n", " TO = true\n", " if affich\n", " println(\"TO\\n\")\n", " end\n", "\n", " elseif (AllDef(x)) # Test de resolution\n", " TR = true\n", " if affich\n", " println(\"TR : solution \", \" de profit \", Objective(x, price), \"\\n\")\n", " end\n", " if (Objective(x, price) >= BestProfit) # Le profit de la solution trouvée est meilleur que les autres\n", " if affich\n", " println(\"\\t-> Cette solution a un meilleur profit.\\n\")\n", " end\n", " # On remplace la solution et le profit par les nouvelles valeurs\n", " Bestsol = x\n", " BestProfit = Objective(x, price)\n", " else\n", " if affich\n", " println(\"\\t-> Cette solution est moins bonne.\\n\")\n", " end\n", " end\n", "\n", " elseif affich\n", " println(\"non sondable\\n\")\n", " end\n", "\n", " return TA, TO, TR, Bestsol, BestProfit\n", "end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Procédure de séparation et stratégie d'exploration permettant de se placer au prochain noeud à traiter" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "SeparerNoeud_relaxlin" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"\"\"Split a node in two.\n", "\n", "Args: \\\\\n", " - price (Vector{Integer}): prices of items to put in the KnapSack. \\\\\n", " - listvars (Vector{Vector{Integer}}): the current values of listvars. \\\\\n", " - listvals (Vector{Integer}): the current values of listvals.\n", "\n", "Returns: \\\\\n", " - listvars (Vector{Vector{Integer}}): the updated values of listvars. \\\\\n", " - listvals (Vector{Integer}): the updated values of listvals.\n", "\"\"\"\n", "function SeparerNoeud_relaxlin(price, listvars, listvals)\n", " # Le noeud est non-sondable. Appliquer le critère de séparation pour le séparer en sous-noeuds \n", "\n", " # Cas du noeud le plus à gauche\n", "\n", " # On sépare le noeud actuel en 2 sous-noeuds\n", " predX = pop!(listvars)\n", " nextX0 = copy(predX)\n", " nextX1 = copy(predX)\n", "\n", " # On initialise leurs valeurs à zéro\n", " val0 = 0\n", " val1 = 0\n", "\n", " # On fixe la nouvelle variable des deux sous-noeuds\n", " n = length(predX)\n", " for i = 1:n\n", " if predX[i] == -1\n", " # L'un a zéro\n", " nextX0[i] = 0\n", " # L'autre a un\n", " nextX1[i] = 1\n", "\n", " # On calcule leurs valeurs\n", " val0 = Objective(nextX0, price)\n", " val1 = Objective(nextX1, price)\n", " break\n", " end\n", " end\n", "\n", " # On ajoute les sous-noeuds a la pile des noeuds a explorer\n", " push!(listvars, nextX0)\n", " push!(listvars, nextX1)\n", "\n", " # On ajoute aussi leurs valeurs\n", " push!(listvals, val0)\n", " push!(listvals, val1)\n", "\n", " return listvars, listvals\n", "end" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "ExplorerAutreNoeud_relaxlin" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"\"\"Pop node fom the list to explore another node.\n", "\n", "Args: \\\\\n", " - price (Vector{Integer}): prices of items to put in the KnapSack. \\\\\n", " - listvars (Vector{Vector{Integer}}): the current values of listvars. \\\\\n", " - listvals (Vector{Integer}): the current values of listvals.\n", "\n", "Returns: \\\\\n", " - listvars (Vector{Vector{Integer}}): the updated values of listvars. \\\\\n", " - listvals (Vector{Integer}): the updated values of listvals. \\\\\n", " - stop (Bool): true if the tree search is finished.\n", "\"\"\"\n", "function ExplorerAutreNoeud_relaxlin(listvars, listvals)\n", " # Le noeud est sondable, on l'enlève de la pile des noeuds à sonder\n", "\n", " stop = false\n", " if (length(listvars) > 1)\n", " # On passe au noeud suivant\n", " var = pop!(listvars)\n", " val = pop!(listvals)\n", " else\n", " # Il n'y a pas d'autre noeud\n", " stop = true\n", " end\n", "\n", " return listvars, listvals, stop\n", "end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fonctions décrivant l'objectif et les contraintes" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "AllDef (generic function with 1 method)" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Fonction objectif que l'on souhaite maximiser/minimiser (évalué dans le meilleur des cas)\n", "Objective(x, price) =\n", " sum(\n", " if x[i] < 0\n", " price[i]\n", " else\n", " price[i] * x[i]\n", " end\n", " for i = 1:length(x)\n", " )\n", "\n", "# Fonction permettant de vérfier toutes les contraintes du modèle (dans le meilleur des cas)\n", "Constraints(x, weight, capacity) =\n", " sum(\n", " if x[i] < 0\n", " 0\n", " else\n", " weight[i] * x[i]\n", " end\n", " for i = 1:length(x)\n", " ) <= capacity\n", "\n", "# Fonction qui nous dis si toutes les variables de x sont fixées\n", "function AllDef(x)\n", " for i = 1:length(x)\n", " if x[i] < 0\n", " return false\n", " end\n", " end\n", " return true\n", "end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Résolution du problème KnapSack" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "SolveKnapInstance" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"\"\"Solve the KnapSack problem for the data contained in `filename`.\n", "\n", "Args: \\\\\n", " - filename (String): the name of the file to read.\n", "\n", "Returns: \\\\\n", " - trParentnodes (Vector{Integer}): the parents nodes, to plot the tree.\n", " - trChildnodes (Vector{Integer}): the child nodes, to plot the tree.\n", " - trNamenodes (Vector{Integer}): the name of the nodes, to plot the tree.\n", "\"\"\"\n", "function SolveKnapInstance(filename)\n", "\n", " stop = false\n", " affich = false\n", "\n", " # Extraction des données\n", " price, weight, capacity = readKnapInstance(filename)\n", "\n", " if affich\n", " println(\"Capacity : \", capacity, \" | Number of objects : \", length(price), \"\\n\")\n", " end\n", "\n", " # Pour dessiner le graph\n", " trParentnodes = Int64[]\n", " trChildnodes = Int64[]\n", " trNamenodes = []\n", "\n", " # Liste des variable pour naviguer de noeuds en noeuds\n", " listvars = []\n", " listvals = []\n", " listnodes = []\n", "\n", " # La meilleur solution et sa valeur\n", " BestProfit = -1\n", " Bestsol = []\n", "\n", " # Compter le nombre de noeud explorés\n", " current_node_number = 0\n", "\n", " # On ajoute le premier noeud à explorer (la racine)\n", " push!(listvars, [-1 for p in price])\n", " push!(listvals, Objective(last(listvars), price))\n", " push!(listnodes, 1)\n", " push!(trNamenodes, 0)\n", " newnodeid = 2\n", "\n", " while (!stop)\n", "\n", " # Le noeud actuel\n", " x = last(listvars)\n", "\n", " if affich && current_node_number % 10000 == 0\n", " println(\"----------\\nNode n°\", current_node_number, \" :\\n\")\n", " println(\"Previous Solution memorized \", \" with bestprofit \", BestProfit, \"\\n\")\n", " end\n", "\n", " # Test de sondabilité du noeud actuel\n", " # -> On mets a jour la solution et sa valeur si besoin\n", " TA, TO, TR, Bestsol, BestProfit = TestsSondabilite_relaxlin(x, price, weight, capacity, BestProfit, Bestsol, affich)\n", "\n", " is_node_sondable = TA || TO || TR\n", " if (!is_node_sondable)\n", " # Le noeud n'est pas sondable, on le sépare en 2 sous-noeuds\n", " listvars, listvals = SeparerNoeud_relaxlin(price, listvars, listvals)\n", "\n", " curnode = pop!(listnodes)\n", "\n", " push!(trParentnodes, curnode)\n", " push!(trParentnodes, curnode)\n", "\n", " push!(listnodes, newnodeid + 1)\n", " push!(listnodes, newnodeid)\n", "\n", " push!(trChildnodes, newnodeid)\n", " push!(trChildnodes, newnodeid + 1)\n", "\n", " push!(trNamenodes, newnodeid - 1)\n", " push!(trNamenodes, newnodeid)\n", "\n", " newnodeid += 2\n", "\n", " else\n", " # Le noeud est sondable, on passe au noeud suivant\n", " listvars, listvals, stop = ExplorerAutreNoeud_relaxlin(listvars, listvals)\n", "\n", " pop!(listnodes)\n", " end\n", "\n", " current_node_number += 1\n", " end\n", "\n", " if affich\n", " println(\"\\n******\\n\\nOptimal value = \", BestProfit, \"\\n\\nOptimal x = \", Bestsol)\n", " end\n", " \n", " return trParentnodes, trChildnodes, trNamenodes\n", "end" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": "\n\n\n \n \n \n\n\n\n \n \n \n\n\n\n \n \n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "trParentnodes, trChildnodes, trNamenodes = SolveKnapInstance(\"data/test.opb\")\n", "graphplot(trParentnodes, trChildnodes, names = trNamenodes, method = :tree)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# # on teste de résoudre tous les fichiers .opb\n", "# for (root, dirs, files) in walkdir(\"data\")\n", "# for file in files\n", "# if endswith(file, \".opb\")\n", "# SolveKnapInstance(root * \"/\" * file)\n", "# end\n", "# end\n", "# end" ] } ], "metadata": { "kernelspec": { "display_name": "Julia 1.6.3", "language": "julia", "name": "julia-1.6" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.6.3" } }, "nbformat": 4, "nbformat_minor": 4 }