diff --git a/notebooks/ms_cflp/cflp.jpg b/notebooks/ms_cflp/cflp.jpg new file mode 100644 index 0000000..b1af683 Binary files /dev/null and b/notebooks/ms_cflp/cflp.jpg differ diff --git a/notebooks/ms_cflp/ms_cflp.ipynb b/notebooks/ms_cflp/ms_cflp.ipynb new file mode 100644 index 0000000..9f85d88 --- /dev/null +++ b/notebooks/ms_cflp/ms_cflp.ipynb @@ -0,0 +1,815 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7677e6a9", + "metadata": {}, + "source": [ + "# Multi source Capacitated Facility Location Problem" + ] + }, + { + "cell_type": "markdown", + "id": "bf2fee57", + "metadata": {}, + "source": [ + "The Multi-Source Capacitated Facility Location Problem (MSCFLP) aims to decide where to open facilities and how to allocate customer demand to them. The goal is to minimize total costs (fixed facility costs and transportation costs) while ensuring each facility's limited capacity isn't exceeded and all customer demands are met. Crucially, customers can be served by multiple facilities in this problem." + ] + }, + { + "cell_type": "markdown", + "id": "b259e859", + "metadata": {}, + "source": [ + "![cflp](cflp.jpg)\n", + "(https://link.springer.com/article/10.1057/s41274-016-0155-6/figures/1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d414905f", + "metadata": {}, + "outputs": [], + "source": [ + "# Install dependencies\n", + "! pip install -q gamspy\n", + "! pip install matplotlib\n", + "! pip install numpy" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "79fce5b3", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "random.seed(42)" + ] + }, + { + "cell_type": "markdown", + "id": "ec9e3787", + "metadata": {}, + "source": [ + "First, we need to define the data that describes our problem. We can open new facilities at every grid point, and our customers are located within a circular area in the middle." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c436059b", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "x_min, x_max = -3, 3\n", + "y_min, y_max = -3, 3\n", + "num_points_per_side = 7\n", + "\n", + "x_grid = np.linspace(x_min, x_max, num_points_per_side)\n", + "y_grid = np.linspace(y_min, y_max, num_points_per_side)\n", + "X_grid, Y_grid = np.meshgrid(x_grid, y_grid)\n", + "\n", + "center_x, center_y = 0, 0\n", + "radius = 1.5\n", + "num_circle_points = 35\n", + "\n", + "angles = 2 * np.pi * np.random.rand(num_circle_points)\n", + "radii = radius * np.sqrt(np.random.rand(num_circle_points))\n", + "\n", + "circle_x_coords = center_x + radii * np.cos(angles)\n", + "circle_y_coords = center_y + radii * np.sin(angles)\n", + "\n", + "\n", + "def plot_locations(facility_selection=None):\n", + "\n", + " plt.figure(figsize=(5, 5))\n", + "\n", + " plt.scatter(\n", + " X_grid, Y_grid, color=\"blue\", label=\"Potential Facility Locations\", alpha=0.7\n", + " )\n", + "\n", + " plt.scatter(\n", + " circle_x_coords, circle_y_coords, color=\"red\", label=\"Customers\", alpha=0.8\n", + " )\n", + "\n", + " if facility_selection is not None:\n", + " plt.scatter(\n", + " facility_selection[0],\n", + " facility_selection[1],\n", + " color=\"green\",\n", + " label=\"Opened Facilities\",\n", + " )\n", + "\n", + " plt.xlabel(\"X-coordinate\")\n", + " plt.ylabel(\"Y-coordinate\")\n", + " plt.grid(True, linestyle=\"--\", alpha=0.6)\n", + " plt.legend(loc=\"upper right\")\n", + " plt.show()\n", + "\n", + "\n", + "plot_locations()" + ] + }, + { + "cell_type": "markdown", + "id": "589d9e5a", + "metadata": {}, + "source": [ + "For our model, we will later need the distances between facilities and customers. Opening facilities in the center is more expensive than opening them on the outskirts. For simplicity, let's assume that all facilities have the same capacity and all customers have the same demand." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "446b4cc9", + "metadata": {}, + "outputs": [], + "source": [ + "facility_coords = np.vstack((X_grid.flatten(), Y_grid.flatten()))\n", + "customer_coords = np.vstack((circle_x_coords.flatten(), circle_y_coords.flatten()))\n", + "\n", + "distances_data = np.zeros((facility_coords.shape[1], customer_coords.shape[1]))\n", + "for i in range(facility_coords.shape[1]):\n", + " for j in range(customer_coords.shape[1]):\n", + " # Euclidean distance formula: sqrt((x2-x1)^2 + (y2-y1)^2)\n", + " distances_data[i, j] = np.sqrt(\n", + " (facility_coords[0, i] - customer_coords[0, j]) ** 2\n", + " + (facility_coords[1, i] - customer_coords[1, j]) ** 2\n", + " )\n", + "\n", + "\n", + "inner_radius = 4 # Facilities within this radius are considered \"middle\"\n", + "expensive_cost_range = (2000, 4000)\n", + "cheap_cost_range = (1000, 2000)\n", + "small_capacity_range = (50, 100)\n", + "big_capacity_range = (100, 200)\n", + "\n", + "\n", + "distances_from_center = np.linalg.norm(\n", + " facility_coords - np.array([[center_x], [center_y]]), axis=0\n", + ")\n", + "\n", + "opening_costs_data = [\n", + " (\n", + " round(np.random.uniform(expensive_cost_range[0], expensive_cost_range[1]))\n", + " if dist <= inner_radius\n", + " else round(np.random.uniform(cheap_cost_range[0], cheap_cost_range[1]))\n", + " )\n", + " for dist in distances_from_center\n", + "]\n", + "\n", + "capacity_data = [200 for _ in range(facility_coords.shape[1])]\n", + "\n", + "demand_data = [50 for _ in range(customer_coords.shape[1])]" + ] + }, + { + "cell_type": "markdown", + "id": "18d480f7", + "metadata": {}, + "source": [ + "### Model Definition\n", + "We need to import GAMSPy and create a container for our model." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "9d368b63", + "metadata": {}, + "outputs": [], + "source": [ + "import gamspy as gp\n", + "\n", + "m = gp.Container()\n" + ] + }, + { + "cell_type": "markdown", + "id": "14c3c7e6", + "metadata": {}, + "source": [ + "Start by eliminating transportation costs; simply beam the goods to the customer.\n", + "\n", + "We need two sets:\n", + "\n", + "$$\n", + "\\begin{array}{rcl}\n", + "\\mathcal{I} :& \\text{Set of potential facilities} \\\\\n", + "\\mathcal{J} :& \\text{Set of customers}\n", + "\\end{array}\n", + "$$\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "35c7cd15", + "metadata": {}, + "outputs": [], + "source": [ + "i = gp.Set(\n", + " m,\n", + " name=\"i\",\n", + " records=[f\"i{i}\" for i in range(facility_coords.shape[1])],\n", + " description=\"Potential facility locations\",\n", + ")\n", + "\n", + "j = gp.Set(\n", + " m,\n", + " name=\"j\",\n", + " records=[f\"j{j}\" for j in range(customer_coords.shape[1])],\n", + " description=\"Customers\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "1c824d13", + "metadata": {}, + "source": [ + "And three parameters\n", + "$$\n", + "\\begin{array}{rcl}\n", + "f_i :& \\text{Fixed cost for opening facility } i \\\\\n", + "q_i :& \\text{Capacity of facility } i \\\\\n", + "d_j :& \\text{Demand of customer } j\n", + "\\end{array}\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f4188133", + "metadata": {}, + "outputs": [], + "source": [ + "opening_cost = gp.Parameter(\n", + " m,\n", + " \"f_i\",\n", + " domain=i,\n", + " records=[[f\"i{i}\", value] for i, value in enumerate(opening_costs_data)],\n", + " description=\"Fixed cost for opening facility i\",\n", + ")\n", + "\n", + "capacity = gp.Parameter(\n", + " m,\n", + " \"q_i\",\n", + " domain=i,\n", + " records=[[f\"i{i}\", value] for i, value in enumerate(capacity_data)],\n", + " description=\"Capacity of facility i\",\n", + ")\n", + "\n", + "demand = gp.Parameter(\n", + " m,\n", + " \"d_j\",\n", + " domain=j,\n", + " records=[[f\"j{j}\", value] for j, value in enumerate(demand_data)],\n", + " description=\"Demand of customer j\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "e1e4b0df", + "metadata": {}, + "source": [ + "Decide for each facility if it will be opened:\n", + "\n", + "$$ Y_i \\in \\lbrace 0, 1\\rbrace \\hspace{1cm} \\forall i\\in \\mathcal{I} \\tag{3} $$" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c3839413", + "metadata": {}, + "outputs": [], + "source": [ + "y = gp.Variable(\n", + " m,\n", + " \"y\",\n", + " type=\"binary\",\n", + " domain=i,\n", + " description=\"Is facility i open\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "31434672", + "metadata": {}, + "source": [ + "We want to minimize the opening costs:\n", + "\n", + "$$\\min \\sum_{i \\in \\mathcal{I} \\hspace{0.75mm} | \\hspace{0.75mm} i < |I|} f_i Y_i \\tag{1} $$" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cb3fbd1d", + "metadata": {}, + "outputs": [], + "source": [ + "obj = gp.Sum(i, opening_cost[i] * y[i])" + ] + }, + { + "cell_type": "markdown", + "id": "90e751d1", + "metadata": {}, + "source": [ + "While fulfilling the demand of all customers:\n", + "\n", + "$$ \\sum_{i\\in \\mathcal{I}} q_i Y_i \\ge \\sum_{j\\in \\mathcal{J}} d_j \\hspace{1cm} \\tag{2} $$" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "309a979e", + "metadata": {}, + "outputs": [], + "source": [ + "facilities_meet_demand = gp.Equation(\n", + " m,\n", + " name=\"facilities_meet_demand\",\n", + " description=\"The facilities need to be able to fulfill the demand\",\n", + ")\n", + "\n", + "# Note this is a scalar equation so we need to access [...]\n", + "facilities_meet_demand[...] = gp.Sum(i, capacity[i] * y[i]) >= gp.Sum(j, demand[j])" + ] + }, + { + "cell_type": "markdown", + "id": "9584b790", + "metadata": {}, + "source": [ + "Now we just combine everything into a model:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9412f9f3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
Solver StatusModel StatusObjectiveNum of EquationsNum of VariablesModel TypeSolverSolver Time
0NormalOptimalGlobal16992.0250MIPCPLEX0.047
\n", + "
" + ], + "text/plain": [ + " Solver Status Model Status Objective Num of Equations Num of Variables \\\n", + "0 Normal OptimalGlobal 16992.0 2 50 \n", + "\n", + " Model Type Solver Solver Time \n", + "0 MIP CPLEX 0.047 " + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cflp_beam = gp.Model(\n", + " m,\n", + " name=\"cflp_beam\",\n", + " equations=[facilities_meet_demand],\n", + " problem=\"MIP\",\n", + " sense=gp.Sense.MIN,\n", + " objective=obj,\n", + ")\n", + "\n", + "cflp_beam.solve(\n", + " solver=\"CPLEX\",\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "id": "b776062c", + "metadata": {}, + "source": [ + "And we can display our solution:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "7e8aa544", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimal solution: 16992.0\n", + "Number of facilities open: 9.0\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "print(f\"Optimal solution: {cflp_beam.objective_value}\")\n", + "print(f\"Number of facilities open: {y.records['level'].sum()}\")\n", + "plot_locations(facility_coords[:, y.records[\"level\"] == 1])" + ] + }, + { + "cell_type": "markdown", + "id": "4272992c", + "metadata": {}, + "source": [ + "Note that, without considering transportation costs, we would obviously open the facilities outside.\n", + "\n", + "Let's save the current solution for y in a parameter so that we can use it for comparisons later." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f1c0d622", + "metadata": {}, + "outputs": [], + "source": [ + "y_beam = gp.Parameter(m, \"y_beam\", domain=i)\n", + "y_beam[i] = y.l[i]" + ] + }, + { + "cell_type": "markdown", + "id": "c079fe78", + "metadata": {}, + "source": [ + "# Expand the Model Yourself" + ] + }, + { + "cell_type": "markdown", + "id": "d5158c47", + "metadata": {}, + "source": [ + "Now let's add transportation costs. We then need to decide which customer is supplied by which facility.\n", + "\n", + "$$\n", + "\\begin{array}{rcl}\n", + "c_{ij} :=& \\text{Unit transportation cost from facility } i \\text{ to customer } j \\\\\n", + "X_{ij} :=& \\text{Fraction of the fulfilled demand of customer } j \\text{ by facility } i \\\\\n", + "\\end{array}\n", + "$$\n", + "\n", + "$$X_{ij} \\in \\left[ 0, 1 \\right] $$" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "8074fff9", + "metadata": {}, + "outputs": [], + "source": [ + "unit_transportation_cost = 200\n", + "\n", + "transportation_cost = gp.Parameter(\n", + " m,\n", + " \"c_ij\",\n", + " domain=[i, j],\n", + " records=[\n", + " [f\"i{i}\", f\"j{j}\", value]\n", + " for i, row in enumerate(unit_transportation_cost*distances_data)\n", + " for j, value in enumerate(row)\n", + " ],\n", + " description=\"Unit transportation cost from facility i to customer j\",\n", + ")\n", + "\n", + "x = gp.Variable(\n", + " m,\n", + " \"x\",\n", + " type=\"positive\",\n", + " domain=[i, j],\n", + " description=\"Fraction of the fulfilled demand of customer j by facility i\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "50dbe671", + "metadata": {}, + "source": [ + " What do we need to add to the objective function?" + ] + }, + { + "cell_type": "markdown", + "id": "a3e33bc0", + "metadata": {}, + "source": [ + "The transportation cost:\n", + "\n", + "$$\\min \\sum_{i \\in \\mathcal{I}} f_i Y_i + \\sum_{i \\in \\mathcal{I}} \\sum_{j \\in \\mathcal{J}} c_{ij} X_{ij}$$" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "fb671474", + "metadata": {}, + "outputs": [], + "source": [ + "obj = gp.Sum(i, opening_cost[i] * y[i]) + gp.Sum(\n", + " gp.Domain(i, j), transportation_cost[i, j] * x[i, j]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "1c85ffe5", + "metadata": {}, + "source": [ + "Need to ensure the whole demand is fulfilled\n", + "\n", + "$$ \\sum_{i\\in \\mathcal{I}} X_{ij} = 1 \\hspace{1cm} \\forall j \\in \\mathcal{J}$$" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "91ea7f79", + "metadata": {}, + "outputs": [], + "source": [ + "demand_fulfillment = gp.Equation(\n", + " m,\n", + " name=\"demand_fulfillment\",\n", + " domain=[j],\n", + " description=\"Every customers demand should be fulfilled\",\n", + ")\n", + "\n", + "demand_fulfillment[j] = gp.Sum(i, x[i,j]) == 1" + ] + }, + { + "cell_type": "markdown", + "id": "80dd2a7d", + "metadata": {}, + "source": [ + "The facilities have a limited capacity. How does this influence the demand fulfillment of the customers?" + ] + }, + { + "cell_type": "markdown", + "id": "395cd716", + "metadata": {}, + "source": [ + "$$ \\sum_{j\\in \\mathcal{J}} d_j X_{ij} \\le q_i Y_i \\hspace{1cm} \\forall i \\in \\mathcal{I}$$" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "640d3802", + "metadata": {}, + "outputs": [], + "source": [ + "capacity_constraints = gp.Equation(\n", + " m,\n", + " name=\"capacity_constraints\",\n", + " domain=i,\n", + " description=\"Facility can't deliver more than its capacity\",\n", + ")\n", + "capacity_constraints[i] = gp.Sum(j, demand[j] * x[i, j]) <= capacity[i] * y[i]" + ] + }, + { + "cell_type": "markdown", + "id": "69de05b0", + "metadata": {}, + "source": [ + "Some facilities will probably stay closed. How does this influence the demand fulfillment of the customers?" + ] + }, + { + "cell_type": "markdown", + "id": "1f7403f5", + "metadata": {}, + "source": [ + "$$ X_{ij} \\le Y_i \\hspace{1cm} \\forall i \\in \\mathcal{I}, \\forall j \\in \\mathcal{J}$$" + ] + }, + { + "cell_type": "markdown", + "id": "45ec5932", + "metadata": {}, + "source": [ + "However, this is implicitly provided by the capacity constraint." + ] + }, + { + "cell_type": "markdown", + "id": "cbafa6c9", + "metadata": {}, + "source": [ + "Combine everything in a model again:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "18acdd75", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimal solution: 28534.904062979345\n", + "Number of facilities open: 9.0\n" + ] + } + ], + "source": [ + "cflp = gp.Model(\n", + " m,\n", + " name=\"cflp\",\n", + " equations=[demand_fulfillment, capacity_constraints],\n", + " problem=\"MIP\",\n", + " sense=gp.Sense.MIN,\n", + " objective=obj,\n", + ")\n", + "\n", + "cflp.solve(\n", + " solver=\"CPLEX\",\n", + ")\n", + "\n", + "print(f\"Optimal solution: {cflp.objective_value}\")\n", + "print(f\"Number of facilities open: {y.records['level'].sum()}\")" + ] + }, + { + "cell_type": "markdown", + "id": "d1bce1e5", + "metadata": {}, + "source": [ + "As you can see, we are now opening facilities in the center to shorten delivery distances." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "cbdfc125", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_locations(facility_coords[:, y.records[\"level\"] == 1])" + ] + }, + { + "cell_type": "markdown", + "id": "b92a0e56", + "metadata": {}, + "source": [ + "Finally, let's compare how much more expansive it would be if we kept the facility selection from our first model." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "2c243283", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimal solution: 33124.17641179543\n", + "Number of facilities open: 9.0\n" + ] + } + ], + "source": [ + "y.fx[i] = y_beam[i]\n", + "\n", + "cflp_fix_y = gp.Model(\n", + " m,\n", + " name=\"cflp_fix_y\",\n", + " equations=[demand_fulfillment, capacity_constraints],\n", + " problem=\"MIP\",\n", + " sense=gp.Sense.MIN,\n", + " objective=obj,\n", + ")\n", + "\n", + "cflp_fix_y.solve(\n", + " solver=\"CPLEX\",\n", + ")\n", + "\n", + "print(f\"Optimal solution: {cflp_fix_y.objective_value}\")\n", + "print(f\"Number of facilities open: {y.records['level'].sum()}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qusol", + "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.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/paintshop/car_sequence.png b/notebooks/paintshop/car_sequence.png new file mode 100644 index 0000000..e2455db Binary files /dev/null and b/notebooks/paintshop/car_sequence.png differ diff --git a/notebooks/paintshop/paintshop.ipynb b/notebooks/paintshop/paintshop.ipynb new file mode 100644 index 0000000..3b9e6c0 --- /dev/null +++ b/notebooks/paintshop/paintshop.ipynb @@ -0,0 +1,823 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Heuristics vs Mathematical Optimization Using the Binary Paintshop Problem\n", + "- In car manufacturing one of the final production steps is painting.\n", + "- Multiple cars of different types (A to D) arrive in a given sequence at the paintshop.\n", + " ![sequence](car_sequence.png)\n", + "- The cars have to be painted with a base coat that is either white or black (here referred to as red or blue).\n", + "- The demand for white and black colors for a given car type is also given.\n", + "\n", + "This problem can be simplified to a minimal working example:\n", + "- In the sequence of cars arriving at the paintshop each vehicle type arrives exactly twice.\n", + "- One car of each vehicle type has to be painted white, the other one has to be painted black.\n", + "\n", + "As changing colors requires time and produces waist, the goal is to minimize the number of color changes with respect to the constraint of coloring one car white and one black for each vehicle type.\n", + "\n", + "This problem can be solved both heuristically or with a mathematical optimization approach." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install dependencies\n", + "! pip install -q gamspy\n", + "! gamspy install solver scip" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "random.seed(16)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sequence = [\"A\", \"D\", \"E\", \"B\", \"A\", \"F\", \"C\", \"B\", \"C\", \"D\", \"E\", \"F\"]\n", + "types = set(sequence)\n", + "n_types = len(types)\n", + "n_types" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Heuristic\n", + "- Start to color every vehicle type white\n", + "- Continue to use white as long as possible\n", + "- Than switch color until every car is painted" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['white', 'white', 'white', 'white', 'black', 'black', 'black', 'black', 'white', 'black', 'black', 'white']\n", + "Number of changes: 4\n" + ] + } + ], + "source": [ + "changes = 0\n", + "colors = {\"white\": set(), \"black\": set()}\n", + "result = []\n", + "\n", + "\n", + "def paint_car(colors_dict, result_list, color, car_type):\n", + " colors_dict[color].add(car_type)\n", + " result_list.append(color)\n", + "\n", + "\n", + "current_color = \"white\"\n", + "for car in sequence:\n", + " if car not in colors[current_color]:\n", + " paint_car(colors, result, current_color, car)\n", + " else:\n", + " # change color\n", + " changes += 1\n", + " if current_color == \"white\":\n", + " current_color = \"black\"\n", + " else:\n", + " current_color = \"white\"\n", + " paint_car(colors, result, current_color, car)\n", + "\n", + "print(result)\n", + "print(\"Number of changes:\", changes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Mathematical Modeling\n", + "\n", + "First we define the necessary sets and our variable $X_i$ which is a binary variable and has domain $i$." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "import gamspy as gp\n", + "from gamspy.math import sqr\n", + "\n", + "# create container\n", + "m = gp.Container()\n", + "\n", + "# create sets\n", + "i = gp.Set(m, \"i\", description=\"number in sequence\")\n", + "j = gp.Set(m, \"j\", description=\"car type\")\n", + "IJ = gp.Set(\n", + " m,\n", + " \"IJ\",\n", + " domain=[i, j],\n", + " records=[(i + 1, sequence[i]) for i in range(len(sequence))],\n", + " domain_forwarding=True,\n", + ")\n", + "\n", + "# create variables\n", + "X = m.addVariable(\"X\", domain=[i], type=\"binary\", description=\"color indicator\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We define our constraint \n", + "\n", + "$$\\sum_{i: (i,j) \\in \\mathcal{IJ}} X_i = 1; \\forall \\ j \\in \\mathcal{J}$$\n", + "\n", + "as an Equation. Since it holds for all $j$ the equation is in the domain $j$. Then we can directly use `gp.Sum()` to define the constraint.\n", + "\n", + "The objective \n", + "$$\\sum_{i \\in \\mathcal{I} \\hspace{0.75mm} | \\hspace{0.75mm} i < |I|} (X_i - X_{i+1})^2$$\n", + "\n", + "is defined as an expression." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "BlackOnce = gp.Equation(\n", + " container=m,\n", + " name=\"BlackOnce\",\n", + " domain=j,\n", + " description=\"Ensure that each position i is painted black exactly once.\",\n", + ")\n", + "\n", + "BlackOnce[j] = gp.Sum(IJ[i, j], X[i]) == 1\n", + "\n", + "obj = gp.Sum(i.where[gp.Ord(i) < gp.Card(i)], sqr(X[i] - X[i + 1]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we assemble everything in our model. Since the objective is a quadratic function the model type is MIQCP (Mixed Integer Quadratically Constrained Program). Now we only need to solve it with a specified solver, here we pick CPLEX." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
Solver StatusModel StatusObjectiveNum of EquationsNum of VariablesModel TypeSolverSolver Time
0NormalOptimalGlobal2.0713MIQCPCPLEX4.672
\n", + "
" + ], + "text/plain": [ + " Solver Status Model Status Objective Num of Equations Num of Variables \\\n", + "0 Normal OptimalGlobal 2.0 7 13 \n", + "\n", + " Model Type Solver Solver Time \n", + "0 MIQCP CPLEX 4.672 " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "paintshop = gp.Model(\n", + " container=m,\n", + " name=\"paintshop\",\n", + " equations=[BlackOnce],\n", + " problem=\"MIQCP\",\n", + " sense=gp.Sense.MIN,\n", + " objective=obj,\n", + ")\n", + "\n", + "paintshop.solve(solver=\"CPLEX\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can display the objective value, meaning the number of changes:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.0" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "opt_changes = paintshop.objective_value\n", + "opt_changes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Or also the value of X for each position:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0 0.0\n", + "1 1.0\n", + "2 1.0\n", + "3 1.0\n", + "4 1.0\n", + "5 1.0\n", + "6 1.0\n", + "7 0.0\n", + "8 0.0\n", + "9 0.0\n", + "10 0.0\n", + "11 0.0\n", + "Name: level, dtype: float64" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X.records[\"level\"]" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compare" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "def display_colored_sequence(method, sequence, color_data):\n", + "\n", + " # Define ANSI color codes\n", + " RED = \"\\033[91m\"\n", + " BLUE = \"\\033[94m\"\n", + " RESET = \"\\033[0m\"\n", + "\n", + " colored_output = []\n", + "\n", + " if method.lower() == \"optimization\":\n", + " # Dynamically assign red to the starting level\n", + " color_map = {color_data[0]: RED, 1 - color_data[0]: BLUE}\n", + " for i, char in enumerate(sequence):\n", + " colored_output.append(color_map[color_data[i]])\n", + " colored_output.append(char)\n", + "\n", + " elif method.lower() == \"heuristic\":\n", + " for i, char in enumerate(sequence):\n", + " # Assign red to 'white' and blue to anything else\n", + " color = RED if color_data[i] == \"white\" else BLUE\n", + " colored_output.append(color)\n", + " colored_output.append(char)\n", + "\n", + " colored_output.append(RESET)\n", + "\n", + " print(\"The coloring sequence is:\", \"\".join(colored_output))\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Optimization" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using the optimization approach 2 color changes are needed.\n", + "The coloring sequence is: \u001b[91mA\u001b[94mD\u001b[94mE\u001b[94mB\u001b[94mA\u001b[94mF\u001b[94mC\u001b[91mB\u001b[91mC\u001b[91mD\u001b[91mE\u001b[91mF\u001b[0m\n" + ] + } + ], + "source": [ + "print(f\"Using the optimization approach {round(opt_changes)} color changes are needed.\")\n", + "display_colored_sequence(\"optimization\", sequence, X.records[\"level\"])" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Heuristic" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using the heuristic approach 4 color changes are needed.\n", + "The coloring sequence is: \u001b[91mA\u001b[91mD\u001b[91mE\u001b[91mB\u001b[94mA\u001b[94mF\u001b[94mC\u001b[94mB\u001b[91mC\u001b[94mD\u001b[94mE\u001b[91mF\u001b[0m\n" + ] + } + ], + "source": [ + "print(f\"Using the heuristic approach {changes} color changes are needed.\")\n", + "display_colored_sequence(\"heuristic\", sequence, result)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Transfer to the Real World - Multi Vehicle Paintshop Problem\n", + "The presented problem is a very easy and simplified version of the real problem where a given number of vehicles of different type arrive in a given sequence at the paint shop and a given share of each vehicle type has to be painted black and the rest white. However, the simplified version gives us a slight impression of how powerful mathematical optimization is. \n", + "Solving the real (multi vehicle paint shop problem) is a more complicated version of the presented (binary paint shop problem). " + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'B': 16, 'F': 4, 'E': 15, 'D': 2, 'A': 3, 'C': 10}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sequence = random.choices(list(types), k=128)\n", + "demand_white = {t: random.randint(0, sequence.count(t)) for t in types}\n", + "demand_black = {t: sequence.count(t) - demand_white[t] for t in types}\n", + "demand_white" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Heuristic" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using the heuristic approach 31 color changes are needed.\n", + "The coloring sequence is: \u001b[91mE\u001b[91mE\u001b[91mE\u001b[91mE\u001b[91mE\u001b[91mD\u001b[91mF\u001b[91mD\u001b[91mB\u001b[91mF\u001b[91mE\u001b[91mB\u001b[91mA\u001b[91mF\u001b[91mA\u001b[91mC\u001b[91mF\u001b[91mC\u001b[91mA\u001b[94mA\u001b[94mB\u001b[94mE\u001b[94mD\u001b[94mF\u001b[94mF\u001b[94mF\u001b[94mF\u001b[94mA\u001b[94mE\u001b[94mE\u001b[94mD\u001b[94mC\u001b[94mF\u001b[94mB\u001b[94mC\u001b[94mA\u001b[94mA\u001b[94mD\u001b[94mF\u001b[94mB\u001b[94mF\u001b[94mE\u001b[94mB\u001b[94mA\u001b[94mA\u001b[94mB\u001b[94mC\u001b[94mE\u001b[94mC\u001b[94mE\u001b[94mA\u001b[94mE\u001b[94mD\u001b[94mE\u001b[94mB\u001b[94mB\u001b[94mF\u001b[94mE\u001b[94mE\u001b[94mD\u001b[94mD\u001b[94mE\u001b[94mF\u001b[94mD\u001b[94mC\u001b[94mA\u001b[94mD\u001b[94mA\u001b[94mF\u001b[94mB\u001b[94mB\u001b[94mB\u001b[94mB\u001b[94mD\u001b[94mC\u001b[91mB\u001b[94mA\u001b[91mC\u001b[94mA\u001b[91mB\u001b[94mD\u001b[94mF\u001b[94mF\u001b[91mB\u001b[94mD\u001b[91mC\u001b[91mB\u001b[91mB\u001b[91mE\u001b[91mB\u001b[91mE\u001b[94mA\u001b[91mE\u001b[91mB\u001b[91mB\u001b[94mF\u001b[94mA\u001b[91mC\u001b[94mD\u001b[91mB\u001b[94mF\u001b[91mE\u001b[91mB\u001b[91mB\u001b[91mE\u001b[91mC\u001b[94mF\u001b[91mC\u001b[91mC\u001b[91mE\u001b[94mF\u001b[94mF\u001b[94mA\u001b[94mA\u001b[91mC\u001b[94mF\u001b[91mE\u001b[91mB\u001b[94mF\u001b[94mF\u001b[91mB\u001b[91mB\u001b[91mC\u001b[94mF\u001b[91mE\u001b[94mD\u001b[91mE\u001b[94mD\u001b[0m\n" + ] + } + ], + "source": [ + "changes = 0\n", + "colors = {\"white\": dict(demand_white), \"black\": dict(demand_black)}\n", + "result = []\n", + "\n", + "\n", + "def paint_car(colors_dict, result_list, color, car_type):\n", + " colors_dict[color][car_type] -= 1\n", + " result_list.append(color)\n", + "\n", + "\n", + "current_color = \"white\"\n", + "for car in sequence:\n", + " if colors[current_color][car] > 0:\n", + " paint_car(colors, result, current_color, car)\n", + " else:\n", + " # change color\n", + " changes += 1\n", + " if current_color == \"white\":\n", + " current_color = \"black\"\n", + " else:\n", + " current_color = \"white\"\n", + " paint_car(colors, result, current_color, car)\n", + "\n", + "\n", + "print(f\"Using the heuristic approach {changes} color changes are needed.\")\n", + "display_colored_sequence(\"heuristic\", sequence, result)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### What modifications do we need to make to the constraint?\n", + "\n", + "So far: $$\\sum_{i: (i,j) \\in \\mathcal{IJ}} X_i = 1; \\forall \\ j \\in \\mathcal{J}$$\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now: \n", + "\n", + "$$\\sum_{i: (i,j) \\in \\mathcal{IJ}} X_i = d^{black}_j \\hspace{1cm} \\forall \\ j \\in \\mathcal{J}$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### What modifications do we need to make to the model implementation?\n", + "\n", + "First need to update the records of the set to account for the longer sequence:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "IJ.setRecords([(i + 1, sequence[i]) for i in range(len(sequence))])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then we need to introduce the new parameters $d_j^{black}$:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# create parameters\n", + "black_demand = gp.Parameter(\n", + " m,\n", + " \"black_demand\",\n", + " domain=[j],\n", + " records=[(type, demand) for type, demand in demand_black.items()],\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And define the corresponding constraints, e.g. Equations:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "MeetBlackDemand = gp.Equation(m, \"MeetBlackDemand\", domain=j)\n", + "MeetBlackDemand[j] = gp.Sum(IJ[i, j], X[i]) == black_demand[j]" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\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", + "
Solver StatusModel StatusObjectiveNum of EquationsNum of VariablesModel TypeSolverSolver Time
0NormalOptimalGlobal1.037MIQCPCPLEX0.032
\n", + "
" + ], + "text/plain": [ + " Solver Status Model Status Objective Num of Equations Num of Variables \\\n", + "0 Normal OptimalGlobal 1.0 3 7 \n", + "\n", + " Model Type Solver Solver Time \n", + "0 MIQCP CPLEX 0.032 " + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "multi_paintshop = gp.Model(\n", + " m,\n", + " \"MultiPaintshop\",\n", + " equations=[MeetBlackDemand],\n", + " problem=\"MIQCP\",\n", + " sense=gp.Sense.MIN,\n", + " objective=obj,\n", + ")\n", + "\n", + "multi_paintshop.solve(solver=\"cplex\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "multi_paintshop.solve(solver=\"scip\")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "# multi_paintshop.solve(solver=\"shot\")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "11.0" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "opt_changes = multi_paintshop.objective_value\n", + "opt_changes" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compare" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Mathematical Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using the optimization approach 11 color changes are needed.\n", + "The coloring sequence is: \u001b[91mE\u001b[91mE\u001b[91mE\u001b[91mE\u001b[91mE\u001b[94mD\u001b[94mF\u001b[94mD\u001b[94mB\u001b[94mF\u001b[94mE\u001b[94mB\u001b[94mA\u001b[94mF\u001b[94mA\u001b[94mC\u001b[94mF\u001b[94mC\u001b[94mA\u001b[94mA\u001b[94mB\u001b[94mE\u001b[94mD\u001b[94mF\u001b[94mF\u001b[94mF\u001b[94mF\u001b[94mA\u001b[94mE\u001b[94mE\u001b[94mD\u001b[94mC\u001b[94mF\u001b[94mB\u001b[94mC\u001b[94mA\u001b[94mA\u001b[94mD\u001b[94mF\u001b[94mB\u001b[94mF\u001b[94mE\u001b[94mB\u001b[94mA\u001b[94mA\u001b[91mB\u001b[91mC\u001b[91mE\u001b[91mC\u001b[91mE\u001b[94mA\u001b[94mE\u001b[94mD\u001b[94mE\u001b[94mB\u001b[94mB\u001b[94mF\u001b[94mE\u001b[94mE\u001b[94mD\u001b[94mD\u001b[94mE\u001b[94mF\u001b[94mD\u001b[94mC\u001b[94mA\u001b[94mD\u001b[94mA\u001b[94mF\u001b[91mB\u001b[91mB\u001b[91mB\u001b[91mB\u001b[91mD\u001b[91mC\u001b[91mB\u001b[91mA\u001b[91mC\u001b[94mA\u001b[94mB\u001b[94mD\u001b[94mF\u001b[94mF\u001b[94mB\u001b[94mD\u001b[91mC\u001b[91mB\u001b[91mB\u001b[91mE\u001b[91mB\u001b[91mE\u001b[91mA\u001b[91mE\u001b[91mB\u001b[91mB\u001b[91mF\u001b[91mA\u001b[91mC\u001b[91mD\u001b[91mB\u001b[91mF\u001b[91mE\u001b[91mB\u001b[91mB\u001b[91mE\u001b[91mC\u001b[91mF\u001b[91mC\u001b[91mC\u001b[91mE\u001b[94mF\u001b[94mF\u001b[94mA\u001b[94mA\u001b[94mC\u001b[94mF\u001b[91mE\u001b[94mB\u001b[94mF\u001b[94mF\u001b[91mB\u001b[91mB\u001b[91mC\u001b[91mF\u001b[91mE\u001b[94mD\u001b[94mE\u001b[94mD\u001b[0m\n" + ] + } + ], + "source": [ + "print(f\"Using the optimization approach {round(opt_changes)} color changes are needed.\")\n", + "display_colored_sequence(\"optimization\", sequence, X.records[\"level\"])" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Heuristic" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using the heuristic approach 31 color changes are needed.\n", + "The coloring sequence is: \u001b[91mE\u001b[91mE\u001b[91mE\u001b[91mE\u001b[91mE\u001b[91mD\u001b[91mF\u001b[91mD\u001b[91mB\u001b[91mF\u001b[91mE\u001b[91mB\u001b[91mA\u001b[91mF\u001b[91mA\u001b[91mC\u001b[91mF\u001b[91mC\u001b[91mA\u001b[94mA\u001b[94mB\u001b[94mE\u001b[94mD\u001b[94mF\u001b[94mF\u001b[94mF\u001b[94mF\u001b[94mA\u001b[94mE\u001b[94mE\u001b[94mD\u001b[94mC\u001b[94mF\u001b[94mB\u001b[94mC\u001b[94mA\u001b[94mA\u001b[94mD\u001b[94mF\u001b[94mB\u001b[94mF\u001b[94mE\u001b[94mB\u001b[94mA\u001b[94mA\u001b[94mB\u001b[94mC\u001b[94mE\u001b[94mC\u001b[94mE\u001b[94mA\u001b[94mE\u001b[94mD\u001b[94mE\u001b[94mB\u001b[94mB\u001b[94mF\u001b[94mE\u001b[94mE\u001b[94mD\u001b[94mD\u001b[94mE\u001b[94mF\u001b[94mD\u001b[94mC\u001b[94mA\u001b[94mD\u001b[94mA\u001b[94mF\u001b[94mB\u001b[94mB\u001b[94mB\u001b[94mB\u001b[94mD\u001b[94mC\u001b[91mB\u001b[94mA\u001b[91mC\u001b[94mA\u001b[91mB\u001b[94mD\u001b[94mF\u001b[94mF\u001b[91mB\u001b[94mD\u001b[91mC\u001b[91mB\u001b[91mB\u001b[91mE\u001b[91mB\u001b[91mE\u001b[94mA\u001b[91mE\u001b[91mB\u001b[91mB\u001b[94mF\u001b[94mA\u001b[91mC\u001b[94mD\u001b[91mB\u001b[94mF\u001b[91mE\u001b[91mB\u001b[91mB\u001b[91mE\u001b[91mC\u001b[94mF\u001b[91mC\u001b[91mC\u001b[91mE\u001b[94mF\u001b[94mF\u001b[94mA\u001b[94mA\u001b[91mC\u001b[94mF\u001b[91mE\u001b[91mB\u001b[94mF\u001b[94mF\u001b[91mB\u001b[91mB\u001b[91mC\u001b[94mF\u001b[91mE\u001b[94mD\u001b[91mE\u001b[94mD\u001b[0m\n" + ] + } + ], + "source": [ + "print(f\"Using the heuristic approach {changes} color changes are needed.\")\n", + "display_colored_sequence(\"heuristic\", sequence, result)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "qusol", + "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.13.5" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tox.ini b/tox.ini index 5ca91e5..a21a883 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,8 @@ commands = notebooks/nurses/nurses.ipynb \ notebooks/pickstock/pickstock.ipynb \ notebooks/transport/trnsport.ipynb \ - notebooks/rowing_optimization/rowing_optimization_model.ipynb + notebooks/rowing_optimization/rowing_optimization_model.ipynb \ + notebooks/paintshop/paintshop.ipynb \ + notebooks/ms_cflp/ms_cflp.ipynb gamspy install solver scip reshop mpsge pytest -x -v -s test_models.py \ No newline at end of file