From d11d5ede42496dc5ca797c6f91dfe6adedb8d609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Thu, 16 Nov 2023 20:22:47 +0100 Subject: [PATCH] Add third notebook using CPLEX callbacks. --- tp2-modeling-and-benders.ipynb | 18 ++-- tp3-docplex-callback.ipynb | 163 +++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 9 deletions(-) create mode 100644 tp3-docplex-callback.ipynb diff --git a/tp2-modeling-and-benders.ipynb b/tp2-modeling-and-benders.ipynb index 4ee3e18..bd0740d 100644 --- a/tp2-modeling-and-benders.ipynb +++ b/tp2-modeling-and-benders.ipynb @@ -47,7 +47,7 @@ "... # TODO\n", "\n", "solution = tsp.solve()\n", - "print(\"z* =\", solution.objective_value)" + "print(\"z* =\", solution.objective_value)\n" ] }, { @@ -90,7 +90,7 @@ "... # TODO: Copy your model from the first question here.\n", "\n", "solution = tsp.solve()\n", - "print(\"z* =\", solution.objective_value)" + "print(\"z* =\", solution.objective_value)\n" ] }, { @@ -147,7 +147,7 @@ "... # TODO: Model for the warehouse allocation.\n", "\n", "solution = wa.solve()\n", - "print(\"z* =\", solution.objective_value)" + "print(\"z* =\", solution.objective_value)\n" ] }, { @@ -230,7 +230,7 @@ "\n", "# Check your method:\n", "wa, z, y = create_master_problem(N, M, f, c)\n", - "print(wa.export_as_lp_string())" + "print(wa.export_as_lp_string())\n" ] }, { @@ -270,7 +270,7 @@ "\n", " Return: The optimality constraint added.\n", " \"\"\"\n", - " return ... # TODO" + " return ... # TODO\n" ] }, { @@ -312,7 +312,7 @@ " The feasibility constraint added.\n", " \"\"\"\n", " # TODO:\n", - " return" + " return\n" ] }, { @@ -370,7 +370,7 @@ "\n", "# Check your method (assuming y = [1 1 1 ... 1]):\n", "dsp, v, u = create_dual_subproblem(N, M, f, c, [1] * M)\n", - "print(dsp.export_as_lp_string())" + "print(dsp.export_as_lp_string())\n" ] }, { @@ -466,7 +466,7 @@ "\n", " ... # TODO\n", "\n", - "print(\"Done.\")" + "print(\"Done.\")\n" ] }, { @@ -509,7 +509,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.9.13" } }, "nbformat": 4, diff --git a/tp3-docplex-callback.ipynb b/tp3-docplex-callback.ipynb new file mode 100644 index 0000000..7f551a5 --- /dev/null +++ b/tp3-docplex-callback.ipynb @@ -0,0 +1,163 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Sequence\n", + "\n", + "\n", + "def find_subtours(x: Sequence[Sequence[int]]) -> Sequence[Sequence[int]]:\n", + " \"\"\"\n", + " Extracts subtours from the given 2D-array.\n", + "\n", + " Args:\n", + " x: A two-dimensional array corresponding to the x variable in the TSP formulation, where\n", + " x[i][j] is 1 if arc (i, j) is used.\n", + "\n", + " Returns:\n", + " A list of subtours, where each subtour is a list.\n", + " \"\"\"\n", + " N = len(x)\n", + " marked = [False] * N\n", + " subtours: list[list[int]] = []\n", + "\n", + " while not all(marked):\n", + " # Index of the first non-marked city:\n", + " istart = min(range(N), key=lambda j: marked[j])\n", + "\n", + " # Create the subtour:\n", + " subtour = [istart]\n", + "\n", + " while True:\n", + " i = istart\n", + " for i, b in enumerate(x[subtour[-1]]):\n", + " if b:\n", + " break\n", + "\n", + " if i == istart:\n", + " break\n", + "\n", + " subtour.append(i)\n", + "\n", + " for i in subtour:\n", + " marked[i] = True\n", + "\n", + " subtours.append(subtour)\n", + "\n", + " return subtours\n" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "699.0\n" + ] + } + ], + "source": [ + "from docplex.mp.model import Model\n", + "from docplex.mp.callbacks.cb_mixin import ConstraintCallbackMixin\n", + "from cplex.callbacks import LazyConstraintCallback\n", + "from docplex.mp.solution import SolveSolution\n", + "import numpy as np\n", + "from numpy.typing import NDArray\n", + "\n", + "import tsp.data\n", + "\n", + "\n", + "class SubtourConstraintCallback(ConstraintCallbackMixin, LazyConstraintCallback):\n", + " x: NDArray\n", + "\n", + " def __init__(self, env):\n", + " LazyConstraintCallback.__init__(self, env)\n", + " ConstraintCallbackMixin.__init__(self)\n", + "\n", + " self.nb_callback = 0\n", + " self.nb_subtours = 0\n", + "\n", + " self.single_subtour_per_call = False\n", + "\n", + " def __call__(self):\n", + " n = len(self.x)\n", + "\n", + " x_list = self.x.flatten().tolist()\n", + "\n", + " sol: SolveSolution = self.make_solution_from_vars(x_list)\n", + "\n", + " xvals = np.array(sol.get_values(x_list)).reshape(self.x.shape).astype(int)\n", + " subtours = find_subtours(xvals)\n", + "\n", + " if len(subtours) == 1:\n", + " return\n", + "\n", + " self.nb_callback += 1\n", + "\n", + " for subtour in subtours:\n", + " xs = [\n", + " self.x[i, j].index\n", + " for i in subtour\n", + " for j in range(n)\n", + " if j not in subtour\n", + " ]\n", + " self.add([xs, [1] * len(xs)], \"G\", 1)\n", + "\n", + " self.nb_subtours += 1\n", + "\n", + " if self.single_subtour_per_call:\n", + " break\n", + "\n", + "\n", + "with Model(name=\"TSP\") as m:\n", + " dist = tsp.data.grid42\n", + " n = len(dist)\n", + "\n", + " x = np.array([m.binary_var_list(n, name=f\"x_{i}\") for i in range(n)])\n", + "\n", + " for i in range(n):\n", + " m.add_constraint(x[i, :].sum() == 1)\n", + " m.add_constraint(x[:, i].sum() == 1)\n", + " m.add_constraint(x[i, i] == 0)\n", + "\n", + " m.set_objective(\"min\", (x * dist).sum())\n", + "\n", + " cb: SubtourConstraintCallback = m.register_callback(SubtourConstraintCallback)\n", + " cb.x = x\n", + "\n", + " s = m.solve()\n", + "\n", + " print(cb.nb_subtours)\n", + " print(s.get_objective_value())\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "INSA", + "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.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}