Created
April 24, 2025 23:37
-
-
Save Financioneroncios/116bdb40e60f738b0861499648033ce8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/" | |
}, | |
"collapsed": true, | |
"id": "DjtRySrkMcBf", | |
"outputId": "99cf4499-f619-4561-87c2-898c71622c1b" | |
}, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"name": "stdout", | |
"text": [ | |
"Collecting riskfolio-lib\n", | |
" Downloading Riskfolio_Lib-7.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (17 kB)\n", | |
"Requirement already satisfied: numpy>=1.24.0 in /usr/local/lib/python3.11/dist-packages (from riskfolio-lib) (2.0.2)\n", | |
"Requirement already satisfied: scipy>=1.10.0 in /usr/local/lib/python3.11/dist-packages (from riskfolio-lib) (1.14.1)\n", | |
"Requirement already satisfied: pandas>=2.0.0 in /usr/local/lib/python3.11/dist-packages (from riskfolio-lib) (2.2.2)\n", | |
"Requirement already satisfied: matplotlib>=3.8.0 in /usr/local/lib/python3.11/dist-packages (from riskfolio-lib) (3.10.0)\n", | |
"Requirement already satisfied: clarabel>=0.6.0 in /usr/local/lib/python3.11/dist-packages (from riskfolio-lib) (0.10.0)\n", | |
"Requirement already satisfied: cvxpy>=1.5.2 in /usr/local/lib/python3.11/dist-packages (from riskfolio-lib) (1.6.5)\n", | |
"Requirement already satisfied: scikit-learn>=1.3.0 in /usr/local/lib/python3.11/dist-packages (from riskfolio-lib) (1.6.1)\n", | |
"Requirement already satisfied: statsmodels>=0.13.5 in /usr/local/lib/python3.11/dist-packages (from riskfolio-lib) (0.14.4)\n", | |
"Collecting arch>=7.0 (from riskfolio-lib)\n", | |
" Downloading arch-7.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (13 kB)\n", | |
"Collecting xlsxwriter>=3.1.2 (from riskfolio-lib)\n", | |
" Downloading XlsxWriter-3.2.3-py3-none-any.whl.metadata (2.7 kB)\n", | |
"Requirement already satisfied: networkx>=3.0 in /usr/local/lib/python3.11/dist-packages (from riskfolio-lib) (3.4.2)\n", | |
"Requirement already satisfied: astropy>=5.1 in /usr/local/lib/python3.11/dist-packages (from riskfolio-lib) (7.0.1)\n", | |
"Collecting pybind11>=2.10.1 (from riskfolio-lib)\n", | |
" Downloading pybind11-2.13.6-py3-none-any.whl.metadata (9.5 kB)\n", | |
"Requirement already satisfied: pyerfa>=2.0.1.1 in /usr/local/lib/python3.11/dist-packages (from astropy>=5.1->riskfolio-lib) (2.0.1.5)\n", | |
"Requirement already satisfied: astropy-iers-data>=0.2025.1.31.12.41.4 in /usr/local/lib/python3.11/dist-packages (from astropy>=5.1->riskfolio-lib) (0.2025.4.21.0.37.6)\n", | |
"Requirement already satisfied: PyYAML>=6.0.0 in /usr/local/lib/python3.11/dist-packages (from astropy>=5.1->riskfolio-lib) (6.0.2)\n", | |
"Requirement already satisfied: packaging>=22.0.0 in /usr/local/lib/python3.11/dist-packages (from astropy>=5.1->riskfolio-lib) (24.2)\n", | |
"Requirement already satisfied: osqp>=0.6.2 in /usr/local/lib/python3.11/dist-packages (from cvxpy>=1.5.2->riskfolio-lib) (1.0.3)\n", | |
"Requirement already satisfied: scs>=3.2.4.post1 in /usr/local/lib/python3.11/dist-packages (from cvxpy>=1.5.2->riskfolio-lib) (3.2.7.post2)\n", | |
"Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->riskfolio-lib) (1.3.2)\n", | |
"Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->riskfolio-lib) (0.12.1)\n", | |
"Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->riskfolio-lib) (4.57.0)\n", | |
"Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->riskfolio-lib) (1.4.8)\n", | |
"Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->riskfolio-lib) (11.1.0)\n", | |
"Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->riskfolio-lib) (3.2.3)\n", | |
"Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.11/dist-packages (from matplotlib>=3.8.0->riskfolio-lib) (2.8.2)\n", | |
"Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.11/dist-packages (from pandas>=2.0.0->riskfolio-lib) (2025.2)\n", | |
"Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.11/dist-packages (from pandas>=2.0.0->riskfolio-lib) (2025.2)\n", | |
"Requirement already satisfied: joblib>=1.2.0 in /usr/local/lib/python3.11/dist-packages (from scikit-learn>=1.3.0->riskfolio-lib) (1.4.2)\n", | |
"Requirement already satisfied: threadpoolctl>=3.1.0 in /usr/local/lib/python3.11/dist-packages (from scikit-learn>=1.3.0->riskfolio-lib) (3.6.0)\n", | |
"Requirement already satisfied: patsy>=0.5.6 in /usr/local/lib/python3.11/dist-packages (from statsmodels>=0.13.5->riskfolio-lib) (1.0.1)\n", | |
"Requirement already satisfied: jinja2 in /usr/local/lib/python3.11/dist-packages (from osqp>=0.6.2->cvxpy>=1.5.2->riskfolio-lib) (3.1.6)\n", | |
"Requirement already satisfied: setuptools in /usr/local/lib/python3.11/dist-packages (from osqp>=0.6.2->cvxpy>=1.5.2->riskfolio-lib) (75.2.0)\n", | |
"Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.11/dist-packages (from python-dateutil>=2.7->matplotlib>=3.8.0->riskfolio-lib) (1.17.0)\n", | |
"Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.11/dist-packages (from jinja2->osqp>=0.6.2->cvxpy>=1.5.2->riskfolio-lib) (3.0.2)\n", | |
"Downloading Riskfolio_Lib-7.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (314 kB)\n", | |
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m314.8/314.8 kB\u001b[0m \u001b[31m6.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", | |
"\u001b[?25hDownloading arch-7.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (985 kB)\n", | |
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m985.3/985.3 kB\u001b[0m \u001b[31m30.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", | |
"\u001b[?25hDownloading pybind11-2.13.6-py3-none-any.whl (243 kB)\n", | |
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m243.3/243.3 kB\u001b[0m \u001b[31m15.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", | |
"\u001b[?25hDownloading XlsxWriter-3.2.3-py3-none-any.whl (169 kB)\n", | |
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m169.4/169.4 kB\u001b[0m \u001b[31m11.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", | |
"\u001b[?25hInstalling collected packages: xlsxwriter, pybind11, arch, riskfolio-lib\n", | |
"Successfully installed arch-7.2.0 pybind11-2.13.6 riskfolio-lib-7.0.0 xlsxwriter-3.2.3\n", | |
"Collecting ecos\n", | |
" Downloading ecos-2.0.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (8.0 kB)\n", | |
"Requirement already satisfied: numpy>=1.6 in /usr/local/lib/python3.11/dist-packages (from ecos) (2.0.2)\n", | |
"Requirement already satisfied: scipy>=0.9 in /usr/local/lib/python3.11/dist-packages (from ecos) (1.14.1)\n", | |
"Downloading ecos-2.0.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (220 kB)\n", | |
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m220.1/220.1 kB\u001b[0m \u001b[31m3.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", | |
"\u001b[?25hInstalling collected packages: ecos\n", | |
"Successfully installed ecos-2.0.14\n" | |
] | |
} | |
], | |
"source": [ | |
"!pip install riskfolio-lib\n", | |
"!pip install ecos" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": { | |
"id": "F1E-UcHwKpqN" | |
}, | |
"outputs": [], | |
"source": [ | |
"import numpy as np\n", | |
"import pandas as pd\n", | |
"import matplotlib.pyplot as plt\n", | |
"import matplotlib.lines as mlines\n", | |
"import seaborn as sns\n", | |
"import cvxpy as cp\n", | |
"import time\n", | |
"import networkx as nx\n", | |
"import riskfolio as rp\n", | |
"import warnings\n", | |
"import random\n", | |
"import time\n", | |
"import itertools\n", | |
"import urllib\n", | |
"import csv\n", | |
"\n", | |
"from scipy.linalg import sqrtm, expm\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": { | |
"id": "2OR6xtfoLJv7" | |
}, | |
"outputs": [], | |
"source": [ | |
"####################################################################################\n", | |
"# Part of this script is taken from the very nice tutorial by Peter Norvig:\n", | |
"# http://nbviewer.ipython.org/url/norvig.com/ipython/TSPv3.ipynb\n", | |
"####################################################################################\n", | |
"\n", | |
"# Cities are represented as Points, which are represented as complex numbers\n", | |
"Point = complex\n", | |
"City = Point\n", | |
"\n", | |
"\n", | |
"def X(point):\n", | |
" \"The x coordinate of a point.\"\n", | |
" return point.real\n", | |
"\n", | |
"\n", | |
"def Y(point):\n", | |
" \"The y coordinate of a point.\"\n", | |
" return point.imag\n", | |
"\n", | |
"\n", | |
"def distance(A, B):\n", | |
" \"The distance between two points.\"\n", | |
" return abs(A - B)\n", | |
"\n", | |
"\n", | |
"def Cities(n, width=900, height=600, seed=42):\n", | |
" \"Make a set of n cities, each with random coordinates within a (width x height) rectangle.\"\n", | |
" random.seed(seed * n)\n", | |
" return frozenset(City(random.randrange(width), random.randrange(height))\n", | |
" for c in range(n))\n", | |
"\n", | |
"\n", | |
"def plot_tour(tour):\n", | |
" \"Plot the cities as circles and the tour as lines between them.\"\n", | |
" plot_lines(list(tour) + [tour[0]])\n", | |
"\n", | |
"\n", | |
"def plot_lines(points, style='bo-'):\n", | |
" \"Plot lines to connect a series of points.\"\n", | |
" plt.plot(list(map(X, points)), list(map(Y, points)), style)\n", | |
" plt.axis('scaled')\n", | |
" plt.axis('off')\n", | |
"\n", | |
"\n", | |
"def tour_length(points, edges):\n", | |
" \"The total of distances between each pair of vertices.\"\n", | |
" points = list(points)\n", | |
" return sum(edges[i, j]*distance(points[i], points[j]) for i, j in edges)\n", | |
"\n", | |
"\n", | |
"def plot_tsp(algorithm, cities):\n", | |
" \"Apply a TSP algorithm to cities, plot the resulting tour, and print information.\"\n", | |
" # Find the solution and time how long it takes\n", | |
" t0 = time.clock()\n", | |
" tour = algorithm(cities)\n", | |
" t1 = time.clock()\n", | |
" assert valid_tour(tour, cities)\n", | |
" plot_tour(tour)\n", | |
" plt.show()\n", | |
" print(\"{} city tour with length {:.1f} in {:.3f} secs for {}\"\n", | |
" .format(len(tour), tour_length(tour), t1 - t0, algorithm.__name__))\n", | |
"\n", | |
"\n", | |
"def valid_tour(tour, cities):\n", | |
" \"Is tour a valid tour for these cities?\"\n", | |
" return set(tour) == set(cities) and len(tour) == len(cities)\n", | |
"\n", | |
"\n", | |
"def plot_labeled_lines(points, args, length):\n", | |
" \"\"\"Plot individual points, labeled with an index number.\n", | |
" Then, args describe lines to draw between those points.\n", | |
" An arg can be a matplotlib style, like 'ro--', which sets the style until changed,\n", | |
" or it can be a list of indexes of points, like [0, 1, 2], saying what line to draw.\"\"\"\n", | |
" # Draw points and label them with their index number\n", | |
" points = list(points)\n", | |
" plot_lines(points, 'bo')\n", | |
" for (label, p) in enumerate(points):\n", | |
" plt.text(X(p), Y(p), ' '+str(label))\n", | |
" # Draw lines indicated by args\n", | |
" style = 'bo-'\n", | |
" for elem in args:\n", | |
" for arg in elem:\n", | |
" if isinstance(arg, str):\n", | |
" style = arg\n", | |
" else: # arg is a list of indexes into points, forming a line\n", | |
" Xs = [X(points[i]) for i in arg]\n", | |
" Ys = [Y(points[i]) for i in arg]\n", | |
" plt.plot(Xs, Ys, style)\n", | |
"\n", | |
" blue_line_full = plt.plot([], [], color='blue', lw=2,\n", | |
" markersize=5, label='$x_{ij}=1$')\n", | |
" red_line_dashed = plt.plot([], [], color='red', ls=\"--\", lw=2,\n", | |
" markersize=5, label='$0.64\\leq x_{ij}<1$')\n", | |
" red_line_dotted = plt.plot([], [], color='red', ls=\"-.\", lw=2,\n", | |
" markersize=5, label='$0.32\\leq x_{ij}<0.64$')\n", | |
" red_line_dd = plt.plot([], [], color='red', ls=':', lw=2,\n", | |
" markersize=5, label='$0.01<x_{ij}<0.32$')\n", | |
"\n", | |
" plt.axis('scaled')\n", | |
" plt.axis('off')\n", | |
" plt.legend(ncol=4,\n", | |
" loc='upper center', bbox_to_anchor=(0.5, 1), bbox_transform=plt.gcf().transFigure,\n", | |
" frameon=None, framealpha=None, fancybox=True)\n", | |
" # fontsize='small')\n", | |
" if length:\n", | |
" plt.text(0.95, 0.03, length,\n", | |
" verticalalignment='bottom', horizontalalignment='right',\n", | |
" transform=plt.gcf().transFigure, style='italic',\n", | |
" bbox={'facecolor': 'red', 'alpha': 0.5, 'pad': 10})\n", | |
"# color='green', fontsize=15)\n", | |
" # plt.legend(loc=1)#handles=[blue_line])\n", | |
" plt.show()\n", | |
"\n", | |
"\n", | |
"def plot_situation(cities, x=[]):\n", | |
" edge_style_list = []\n", | |
" for (i, j) in x:\n", | |
" if x[i, j] > 0.98:\n", | |
" edge_style_list.append(['b-', (i, j)])\n", | |
" elif x[i, j] > 0.64:\n", | |
" edge_style_list.append(['r--', (i, j)])\n", | |
" elif x[i, j] > 0.32:\n", | |
" edge_style_list.append(['r-.', (i, j)])\n", | |
" elif x[i, j] > 0.02:\n", | |
" edge_style_list.append(['r:', (i, j)])\n", | |
" else:\n", | |
" edge_style_list.append([' ', (i, j)])\n", | |
" if len(x) > 0:\n", | |
" length = \"Tour length {:.1f}\".format(tour_length(cities, x))\n", | |
" else:\n", | |
" length = None\n", | |
" plot_labeled_lines(cities, edge_style_list, length)\n", | |
"\n", | |
"\n", | |
"def read_instance(filename):\n", | |
" if filename[-3:] == \"tsp\":\n", | |
" import tsplib95\n", | |
" with open(filename, \"r\") as f:\n", | |
" problem = tsplib95.read(f)\n", | |
"\n", | |
" print(list(problem.get_nodes()))\n", | |
" print(list(problem.get_edges())) # I'll spare you the full listing :P\n", | |
"\n", | |
" print(problem.node_coords)\n", | |
" print(problem.node_coords)\n", | |
" print(problem.edge_weight_type)\n", | |
" edge = 3, 8\n", | |
" weight = problem.get_weight(*edge)\n", | |
" print(\n", | |
" f'The driving distance from node {edge[0]} to node {edge[1]} is {weight}.')\n", | |
" G = problem.get_graph()\n", | |
" E = list(G.edges())\n", | |
" print(E[0])\n", | |
" print(G.edges[E[0]][\"weight\"])\n", | |
" for node1, node2, data in G.edges(data=True):\n", | |
" print(data['weight'])\n", | |
" else:\n", | |
" inputDataFile = open(filename, \"r\")\n", | |
" lines = inputDataFile.readlines()\n", | |
" inputDataFile.close()\n", | |
" Xs = []\n", | |
" Ys = []\n", | |
" for line in lines:\n", | |
" if line[0] == \"#\":\n", | |
" continue\n", | |
" parts = line.split()\n", | |
" Xs.append(float(parts[1]))\n", | |
" Ys.append(float(parts[2]))\n", | |
" #return frozenset(City(Xs[c], Ys[c]) for c in range(len(Xs)))\n", | |
" G = nx.Graph()\n", | |
" for c in range(len(Xs)):\n", | |
" G.add_node(c, pos=(Xs[c], Ys[c]))\n", | |
" # G.add_edges_from(itertools.permutations(G.nodes, 2))\n", | |
" for arc in itertools.permutations(G.nodes, 2):\n", | |
" G.add_edge(*arc, weight=distance(City(Xs[arc[0]],Ys[arc[0]]), City(Xs[arc[1]],Ys[arc[1]])))\n", | |
" nx.draw_networkx_nodes(\n", | |
" G, pos=nx.get_node_attributes(G, 'pos'), node_size=50)\n", | |
" plt.draw()\n", | |
" plt.show()\n", | |
" return G\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": { | |
"id": "VDHHMH5wLPCo" | |
}, | |
"outputs": [], | |
"source": [ | |
"###############################################################################\n", | |
"# Auxiliary Functions\n", | |
"###############################################################################\n", | |
"\n", | |
"# Function that generates random instances for the TSP\n", | |
"def random_cities(n, width=3000, height=1900, seed=1):\n", | |
" ran_points = list(Cities(n=n, width=width, height=height, seed=seed)) #3596 #12\n", | |
"\n", | |
" points=list(ran_points)\n", | |
" n = len(ran_points)\n", | |
" V = list(range(len(ran_points)))\n", | |
" E = [(i,j) for i in V for j in V if i<j] # complete graph\n", | |
"\n", | |
" cost = {e: distance(points[e[0]],points[e[1]]) for e in E}\n", | |
"\n", | |
" C_d = np.zeros((len(ran_points), len(ran_points)))\n", | |
" for e in E:\n", | |
" C_d[e[0],e[1]] = distance(points[e[0]],points[e[1]])\n", | |
" C_d[e[1],e[0]] = C_d[e[0],e[1]]\n", | |
"\n", | |
" return C_d, ran_points" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": { | |
"id": "UitFT98gLsrF" | |
}, | |
"outputs": [], | |
"source": [ | |
"####################################################################################\n", | |
"# Semidefinite programming TSP based on QAP and GIP proposed by Cajas (2025)\n", | |
"####################################################################################\n", | |
"\n", | |
"def SDP_STSP_Cajas(C_d, solver='SCS'):\n", | |
" n, _ = C_d.shape\n", | |
"\n", | |
" A_c = nx.adjacency_matrix(nx.cycle_graph(n=n)).toarray()\n", | |
" L2 = rp.duplication_elimination_matrix(n, False)\n", | |
" B = nx.incidence_matrix(nx.complete_graph(n))\n", | |
" D_c = np.kron(A_c, C_d)\n", | |
" vec_A_c = A_c.reshape((-1,1), order='F')\n", | |
" vec_C_d = C_d.reshape((-1,1), order='F')\n", | |
" ones_n = np.ones((n, 1))\n", | |
" ones_n2 = np.ones((n**2, 1))\n", | |
" opt_time = 0\n", | |
"\n", | |
" Y = cp.Variable((n**2, n**2), symmetric=True)\n", | |
" X = cp.Variable((n, n))\n", | |
" vec_X = cp.reshape(X, (-1,1), order='F')\n", | |
" M = cp.bmat([[Y, vec_X], [vec_X.T, np.ones((1,1))]])\n", | |
"\n", | |
" for j in range(n):\n", | |
" Aj = cp.reshape(Y[j * n: j * n + 1].T, (n, n), order=\"F\")\n", | |
" for i in range(1,n):\n", | |
" Aij = cp.reshape(Y[i + j * n : i + j * n + 1].T, (n, n), order=\"F\")\n", | |
" Aj = cp.vstack([Aj, Aij])\n", | |
" if j == 0:\n", | |
" Z = Aj\n", | |
" else:\n", | |
" Z = cp.hstack([Z, Aj])\n", | |
"\n", | |
" # Defining the objective function\n", | |
" cost = vec_C_d.T @ (Z @ vec_A_c)/2\n", | |
" objective = cp.Minimize(cost)\n", | |
"\n", | |
" # Defining the constraints\n", | |
" constraints = []\n", | |
" constraints += [M >> 0]\n", | |
" constraints += [Z @ ones_n2 == 1]\n", | |
" constraints += [Z.T @ ones_n2 == 1]\n", | |
" constraints += [cp.reshape(cp.diag(Y), (-1,1), order='F') - vec_X == 0]\n", | |
" constraints += [Y @ ones_n2 - n * vec_X == 0]\n", | |
" constraints += [B @ L2 @ Z @ vec_A_c == 2]\n", | |
" constraints += [Y >= 0, Y <= 1]\n", | |
" constraints += [X >= 0, X <= 1]\n", | |
" constraints += [X @ ones_n == 1, X.T @ ones_n == 1]\n", | |
" constraints += [X[0,1] == 1]\n", | |
"\n", | |
" # Solving the problem\n", | |
" prob = cp.Problem(objective, constraints)\n", | |
" start_time = time.time()\n", | |
" if solver == 'SCS':\n", | |
" prob.solve(solver=solver,\n", | |
" verbose=True,\n", | |
" warm_start=False,\n", | |
" max_iters=400000,\n", | |
" eps=1e-8,\n", | |
" )\n", | |
" else:\n", | |
" prob.solve(solver=solver,\n", | |
" verbose=True,\n", | |
" warm_start=False,\n", | |
" )\n", | |
"\n", | |
" display(np.round(Y.value))\n", | |
"\n", | |
" opt_time += (time.time() - start_time)\n", | |
"\n", | |
" X1 = (Z.value @ vec_A_c).reshape((n,n), order='F')\n", | |
"\n", | |
" X_sol = np.argwhere(np.triu(np.round(X1))==1)\n", | |
" orden = {}\n", | |
" for i,j in X_sol:\n", | |
" orden[(i,j)] = 1\n", | |
"\n", | |
" cost_opt = cost.value.item()\n", | |
"\n", | |
" return orden, X1, cost_opt, opt_time" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 31, | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 564 | |
}, | |
"id": "TyYvaHvRLcbn", | |
"outputId": "3b7459ed-78be-4547-cba7-fa326bdf0570" | |
}, | |
"outputs": [ | |
{ | |
"output_type": "display_data", | |
"data": { | |
"text/plain": [ | |
"<Figure size 1200x600 with 1 Axes>" | |
], | |
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAm0AAAIjCAYAAABRWSyiAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAL79JREFUeJzt3Xt41NWdx/HPMCHhGkMwmEAIoaIgChhkvaDQBBAoLUIjugUt4rLx8rAuqGsRVhdcL/XZ0m4oi8VUBNcKUmNwgQIPCAQighWEAoooGAq5DEi5aBNuTs7+MSYk5D6ZycyZeb+eZ56QM+f3yzeTkx+fnPP7/cZhjDECAABAUGsR6AIAAABQP0IbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFggorEbuN1uXbx40R+1AAAAhI2WLVvK6XQ2uH+DQ5sxRi6XS6dPn/amLgAAAFwmJiZG8fHxcjgc9fZtcGgrD2ydOnVSmzZtGrRzAAAAVGeMUWlpqY4fPy5JSkhIqHebBoU2t9tdEdg6duzYtCoBAACg1q1bS5KOHz+uTp061btU2qALEcrPYWvTpk0TywMAAEC58mzVkOsFGnX1KEuiAAAAvtOYbMUtPwAAACxAaAMAALAAoc2PtmzZotGjR6tz585yOBx67733Al2SFebPn6/k5GS1atVKt9xyi/785z/Xu01hYaHuv/9+dezYUa1bt1afPn20Y8eOGvu+/PLLcjgcmjZtmo8rDxzGmncaO9Z+97vfqW/fvoqOjlZ0dLRuu+02rVmzpkqfX/7yl/qHf/gHtW/fXp06ddLYsWN14MABf34bzc6b31GJ31N/8ebnUd824XBM8eZ1C/QxgNDmRyUlJerXr5/mz58f6FKssWzZMj3xxBOaNWuWPvnkE/Xr108jRoyouCS6JqdOndLtt9+uli1bas2aNfrss8/061//Wh06dKjW9+OPP9arr76qvn37el3jxYsXA3aD6VOnTunvf/97tXbGWuN5M9YSExP18ssva+fOndqxY4eGDBmiMWPG6NNPP63os3nzZk2ZMkXbt2/X+vXrdfHiRQ0fPlwlJSWNrjEYx5o3r1v5/prz9zRcePPzaMg2vjym1DaWmoOvx3FzHwOqMQ1w9uxZ89lnn5mzZ882pHtQWbJkiWnVqpUpKiqqaJs0aZLp06ePOX36dLPVIcksX7682b6erW6++WYzZcqUis/dbrfp3Lmz+eUvf1nrNtOnTzd33HFHvfv+9ttvzTXXXGPWr19vfvjDH5qpU6c2uK7CwkLz2muvmfT0dBMdHW32799frY+/xtrFixfNqlWrzLhx40xUVJTZvXt3nf0Zaw3jzVirSYcOHcxrr71W6/PHjx83kszmzZsbtL9gH2vevm7N8Xsajrz5eTR2G2+OKQ0ZSzaO45o09RjQmIwV8jNtP/vZz3TttdfqpZdekiTNmjVL77//vtasWaMrrriiQft46aWX1K5duzofR44c8ee3ERYuXLignTt3atiwYRVtLVq00LBhw7Rt27Zat1uxYoUGDBige+65R506dVJKSop+//vfV+s3ZcoU/fjHP66y/9q43W59+OGHeuaZZ9S/f38lJiYqMzNT11xzjVatWqVrr7222ja+GGuV7d27V08++aQSExM1ceJExcXFadOmTerXr1+j94WqvB1rlbndbr399tsqKSnRbbfdVmu/M2fOSJJiY2Nr3Y8tY60pr5s/fk/DnTc/D1+M/bo05rhl4ziuzFfHgEZpSIq0eabNGGNWrlxpoqKizAsvvGA6dOhg9u3bV/Hchg0bzJw5c4wxxmzdutW88MIL1bb/29/+Zr788ss6HxcvXqyzBjH7Ua/CwkIjyXz44YdV2p966ilz880317pdVFSUiYqKMjNmzDCffPKJefXVV02rVq3M4sWLK/osXbrU3HDDDRVjuK6/4F9//XUTGxtroqKizKhRo8z8+fPN4cOHG/Q91DXWjLk03mobaydOnDCZmZkmJSXFREZGmrFjx5p3333XnD9/vkFf3xjGWkN4O9aMMWbPnj2mbdu2xul0miuuuML86U9/qrWv2+02P/7xj83tt99e4/O2jbWmvG6+/j2Fdz8Pb7ap75jSlOOWbePYGN8eA4xpXMZq9BvGVzZggORyNTE1NlJ8vFTLeau1+slPfqLevXvrP//zP7Vu3Tpdf/31Fc8NGTJEQ4YMkSQNHDhQAwcOrLZ9bGysbxJyoP3mN55Hffr3l1asqNp2113SJ5/Uv+0TT3gezaisrEwDBgyo+GstJSVF+/bt04IFC/TAAw/o6NGjmjp1qtavX69WrVrVu78OHTooMTFRe/fulcvlUnFxsYqKitS1a1e1aFH35HRdY02qPt4uN2/ePD333HMaNGiQDh48qK5duzb0ZQg+DR1v9fnDH6TU1Euf5+ZK99/v+XcAxlvPnj21e/dunTlzRtnZ2XrggQe0efNm9e7du1rfKVOmaN++ffrggw9q3Fc4jTVf/542i8pj+PJxmJ8vDRrk+fdPfyrNm1d128rHzIKCqs8tXiw984zn37/9rZSe7uvKm1VTxpJt41jy7TGg0eqNdXWkwC5djJGa99GlS0MqrmrNmjWmdevWxul0Vjs/ZPTo0WbPnj3GGGPGjRtnPv7442rbv/jii6Zt27Z1Pv7617/WWYOCYfZj1qyGvci33lp921tvbdi2s2Z5Xd758+eN0+ms9jpNnDjR3HXXXbVul5SUZCZPnlyl7ZVXXjGdO3c2xhizfPlyI8k4nc6KhyTjcDiM0+k03333XY37LSgoMFlZWWbs2LGmffv2JjY21owfP968+eab5ty5czVuU9dYM+bSeKttrBUWFprnn3/eXHPNNaZ9+/Zm0qRJZsOGDcbtdtf6/V8uKMaaMQ0fb/U91q6tut+1a5s83rwdazUZOnSoeeihh6q1T5kyxSQmJpqvvvqq3n3YMtaa8rr56/fUryqP4cvH4ZdfXnruvvuqb1v5mHm5//mfS8+9+abX5Xnz8/Bmm/qOKU05btk2jmvS1GNAs53TFh8vdenSvI/4+MbV+Mknn+jee+/VwoULNXToUD377LNVnv/888/Vq1cvSdKnn35aLeVL0iOPPKLdu3fX+ejcubPXr2OziY5u2IscF1d927i4hm0bHe11eZGRkbrpppu0YcOGiraysjJt2LChzvMFbr/99mqXU3/xxRfq1q2bJGno0KHau3dvlZ/XgAEDdN9992n37t21vtdbly5dlJGRoeXLl+vEiRNatmyZ4uPj9cILL+jw4cPV+tc31qRL4622sda5c2c988wz+uKLL7R27VpFRkYqPT1d3bp109NPP13lCqWg19DxVt8jKqrqfqOimjzevB1rNSkrK9P58+crPjfG6F/+5V+0fPlybdy4Ud27d693H7aMtaa8bv76PfWrymP48nHodF56roYrYKscMy/Xtu2l55rw9pDe/Dx8OfbLeXvcsnEc18QXx4AGa0iKtPWctvz8fBMfH19xNcj27duNw+EwO3fuNMYY880335g+ffoYY4wpKSkxvXv39unX//bbb82uXbvMrl27jCTzm9/8xuzataveWblw9vbbb5uoqCizePFi89lnn5mHHnrIxMTEGJfLZYwxZt68eWbIkCFVtvnzn/9sIiIizIsvvmi+/PJL89Zbb5k2bdqYP/zhD7V+nbrOlTl+/LjZv39/nY/Lz5eob6wZc2m8NXasnT171ixdutSMGDHCOJ3OipnhyhhrjefNWHv66afN5s2bTX5+vtmzZ495+umnjcPhMOvWravo8+ijj5orrrjC5ObmmuLi4opHaWlptRpsHGv1vW61vXa+/j2FhzfjuCE/w6YeU+obS7aOY18fA8rrbWjGCtnQ9re//c307NnTPPzww1XaR40aZUaMGGGMMWbbtm3mZz/7mTHGmI8++sjcc889Pq1h06ZNRlK1xwMPPODTrxNq5s2bZ5KSkkxkZKS5+eabzfbt2yuemzVrlunWrVu1bVauXGluuOEGExUVZXr16mWysrLq/Bp1/Wcwffr0Gn9ulR+Vp/EbMtaMuTTemjLWCgsLzZkzZ6q1M9a809ix9k//9E+mW7duJjIy0sTFxZmhQ4dWOVgbY2odM4sWLar29W0ca8bU/boZ0zy/p7jEm2NmfT9DXx5TLh9LNo9jXx8DjGlcxnJ8/wXqdO7cOeXn56t79+7Bc4KoD/z+97/X119/rZkzZ+q1116Ty+XSM+UnhwI+Vj7eOnXqxFiDXzHWEArCZRw3JmOF/H3a6rJ3717dcMMNkqQ9e/Zw9234Vfl4Y6zB3xhrCAWM4+rCeqatsv79++tPf/qTEhISAl0KQhxjDc2FsYZQEOrjmJm2Rrhw4YL69++vkSNHhuyAQHBgrKG5MNYQChjH1THTBgAAECDMtAEAAIQYQhsAAIAFCG0AAAAWILQBAABYgNAGAABggUaFtrKyMn/VAQAAEHYak60iGtIpMjJSLVq0UFFRkeLi4hQZGSmHw+F1gQAAAOHMGKMLFy7o66+/VosWLRQZGVnvNg26T5vkucldcXGxSktLm1woAAAApDZt2ighIcG3oU3ypMLvvvtObre7SQUCAACEO6fTqYiIiAavXjYqtAEAACAwuHoUAADAAoQ2AAAACxDaAAAALEBoAwAAsAChDQAAwAKENgAAAAsQ2gAAACxAaAMAALAAoQ0AAMAChDYAAAALENoAAAAsQGgDAACwAKENAADAAoQ2AAAACxDaAAAALEBoAwAAsAChDQAAwAKENgAAAAsQ2gAAACxAaAMAALAAoQ0AAMAChDYAAAALENoAAAAsQGgDAACwAKENAADAAoQ2AAAACxDaAAAALEBoAwAAsAChDQAAwAKENgAAAAsQ2gAAACxAaAMAALAAoQ0AAMAChDYAAAALENoAAAAsQGgDAACwAKENAADAAoQ2AAAACxDaAAAALEBoAwAAsAChDQAAwAKENgAAAAsQ2gAAACxAaAMAALAAoQ1AwB0+fFgOh6PGxzvvvBPo8gAgKDiMMSbQRQAIb263W19//XWVtqysLP3qV79ScXGx2rVrF6DKACB4ENoABKWUlBT1799fCxcuDHQpABAUWB4FEHR27typ3bt3a/LkyYEuBQCCBqENQNBZuHChrrvuOg0cODDQpQBA0IgIdAEAQpPbLeXlScXFUkKCNGiQ5HTWv93Zs2e1ZMkSPfvss/4vEgAsQmgD4HM5OdLUqVJBwaW2xERp7lwpPb3ubbOzs1VaWqqJEyf6t0gAsAwXIgDwqZwcadw46fIji8Ph+ZidXXdwS01N1ZVXXqns7Gz/FQkAFiK0AfAZt1tKTq46w1aZw+GZccvPr3mp9ODBg7r22mu1evVqjRw50q+1AoBtuBABgM/k5dUe2CTP7NvRo55+NXn99deVmJio4cOH+6dAALAYoQ2AzxQXN63fSy+9pCNHjqhFCw5NAHA5jowAfCYhwbf9AACXcE4bAJ8pP6etsLD6hQhS/ee0AQBqx0wbAJ9xOj239ZAuXS1arvzzzEwCGwB4g9AGwKfS0z239ejSpWp7YmL9t/sAANSO5VEAfuHtOyIAAGpGaAMAALAAy6MAAAAWILQBAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABQhtQBOdPHlSjz32mHr27KnWrVsrKSlJ//qv/6ozZ84EujQAQAiJCHQBgO2KiopUVFSkOXPmqHfv3vrrX/+qRx55REVFRcrOzg50eQCAEOEwxphAFwGEmnfeeUf333+/SkpKFBHB30YAgKZjeRTwgzNnzig6OprABgDwGUIb4GMnTpzQ888/r4ceeijQpQAAQgjLo0AN3G4pL08qLpYSEqRBgySns/7tvvnmG915552KjY3VihUr1LJlS/8XCwAIC4Q24DI5OdLUqVJBwaW2xERp7lwpPb327b799luNGDFCbdq00apVq9SqVSv/FwsACBuENqCSnBxp3Djp8t8Kh8PzMTu75uD2zTffaMSIEYqKitLq1avVpk0b/xcLAAgrhDbge263lJxcdYatMofDM+OWn191qfSbb77R8OHDVVpaquXLl6tt27YVz8XFxcnZkHVVAADqQWgDvpebK6Wl1d9v0yYpNbXydrlKq2XD/Px8JScn+6I8AECY434EwPeKi73rl5qaKv72AQD4G7f8AL6XkODbfgAA+BLLo8D3ys9pKyysfiGCVPs5bQAANAdm2oDvOZ2e23pIl64WLVf+eWYmgQ0AEBiENqCS9HTPbT26dKnanphY++0+AABoDiyPAjXw9h0RAADwF0IbAACABVgeBQAAsAChDQAAwAKENgAAAAsQ2gAAACxAaAMAALAAoQ0AAMAChDYAAAALENoAAAAsQGgDAACwAKENAADAAoQ2AAAACxDaAAAALEBoAwAAsAChDQAAwAKENgAAAAsQ2gAA+F5WVpZSU1MVHR0th8Oh06dPV3n+8OHDmjx5srp3767WrVvr6quv1qxZs3ThwoXAFIywEhHoAgAACBalpaUaOXKkRo4cqRkzZlR7/vPPP1dZWZleffVV9ejRQ/v27VNGRoZKSko0Z86cAFSMcOIwxphAFwEAQDDJzc1VWlqaTp06pZiYmDr7/upXv9Lvfvc7ffXVV81THMIWy6MAADTBmTNnFBsbG+gyEAYIbQAAeOngwYOaN2+eHn744UCXgjDAOW0AgJDldkt5eVJxsZSQIA0aJDmdvtl3YWGhRo4cqXvuuUcZGRm+2SlQB0IbACAk5eRIU6dKBQWX2hITpblzpfT0pu27qKhIaWlpGjhwoLKyspq2M6CBWB4FAIScnBxp3LiqgU2SCgs97Tk53u+7sLBQqampuummm7Ro0SK1aMF/pWgeXD0KAAgpbreUnFw9sJVzODwzbvn51ZdKXS6XXC6XduzYoYyMDG3ZskXt27dXUlKSYmNjKwJbt27d9MYbb8hZaQfx8fH++6YAEdoAACEmN1dKS6u/36ZNUmpq1bbZs2frueeeq9Z30aJFmjRpkhYvXqwHH3ywxv3x3yn8jdAGAAgpS5dKEybU32/JEmn8eP/XA/gKC/EAgJCSkODbfkCwYKYNABBSys9pKyyUavofrq5z2oBgxkwbACCkOJ2e23pInoBWWfnnmZkENtiH0AYACDnp6VJ2ttSlS9X2xERPe1Pv0wYEAsujAICQ5c93RACaG6ENAADAAiyPAgAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAiorK0upqamKjo6Ww+HQ6dOnq/W56667lJSUpFatWikhIUE///nPVVRU1PzFArVoyDgud/78ed14441yOBzavXt3s9UI+xHaAARUaWmpRo4cqZkzZ9baJy0tTX/84x914MABvfvuuzp06JDGjRvXjFUCdWvIOC73i1/8Qp07d26GqhBqHMYYE+giACA3N1dpaWk6deqUYmJi6uy7YsUKjR07VufPn1fLli2bp0CgAeobx2vWrNETTzyhd999V9dff7127dqlG2+8sdnrhJ2YaQMaKTU1VQ6Ho8rjkUceCXRZYePkyZN66623NHDgQAIbrHLs2DFlZGTozTffVJs2bQJdDixEaAO8kJGRoeLi4orHf/3XfwW6pJA3ffp0tW3bVh07dtSRI0f0f//3f4EuCWgwY4wmTZqkRx55RAMGDAh0ObAUoQ3wQps2bRQfH1/xiI6ODnRJQcPtlnJzpaVLPR/dbt/s96mnntKuXbu0bt06OZ1OTZw4UZzdAX/x9TieN2+evv32W82YMcMX5SFMEdoAL7z11lu68sordcMNN2jGjBkqLS0NdElBISdHSk6W0tKkCRM8H5OTPe1NdeWVV+raa6/VnXfeqbffflurV6/W9u3bm75j4DL+GMcbN27Utm3bFBUVpYiICPXo0UOSNGDAAD3wwAM+qRuhLyLQBQC2mTBhgrp166bOnTtrz549mj59ug4cOKAcXyQTi+XkSOPGSZdPfhUWetqzs6X0dN98rbKyMkmeWycAvuSvcfzb3/5WL7zwQsXnRUVFGjFihJYtW6ZbbrmliVUjXHD1KMKe2y3l5UnFxVJCgjRokOR0Nnz7jRs3aujQoTp48KCuvvpq/xUaxNxuz0xEQUHNzzscUmKilJ9f/bV1uVxyuVzasWOHMjIytGXLFrVv315JSUmKjY3VRx99pI8//lh33HGHOnTooEOHDunZZ5/VsWPH9OmnnyoqKsrv3x/Cgz/H8eUOHz6s7t27++zqUWOMRo0apbVr12r58uUaO3Zsk/eJ4MPyKMKaL5ZByv9KPnjwoF9qtEFeXu3/0UmeWYujRz39LrdgwQKlpKQoIyNDkjR48GClpKRoxYoVkjznD+bk5Gjo0KHq2bOnJk+erL59+2rz5s0ENviUP8exv2VmZsrhcDTL10LgMNOGsFXbMkj5ca+hyyBbt27VHXfcob/85S/q27ev7wu1wNKlntBbnyVLpPHj/V8P4A1bx/Hu3bv1k5/8RDt27FBCQgIzbSGMmTaEJbdbmjq1emCTLrVNm1b9irFDhw7p+eef186dO3X48GGtWLFCEydO1ODBg8M2sEmeZWVf9gMCwcZxXFpaqgkTJmj+/PmKj48PdDnwM0IbwpK3yyCRkZF6//33NXz4cPXq1UtPPvmk7r77bq1cudK/BQe5QYM85/rUtjrjcEhdu3r6AcHKxnH8+OOPa+DAgRozZkygS0Ez4OpRhKXiYu/6de3aVZs3b/Z9QZZzOqW5cz3LzQ5H1RnM8v8AMzMbd4EH0NyCYRw35sKoFStWaOPGjdq1a5f/CkJQYaYNYcnGZZBgl57uOQ+wS5eq7YmJvr3dB4LHww8/rKuvvlqtW7dWXFycxowZo88//zzQZTVJIMdxYy+M2rhxow4dOqSYmBhFREQoIsIzD3P33XcrNTXVf4UiYLgQAWGp/NL+wsKaz2ur69J+1K2pt1CBPbKystSrVy8lJSXp5MmTmj17tnbv3q38/Hw5Lf+hN/c49ubCKJfLpRMnTlRp69Onj+bOnavRo0ere/fu/isYAUFoQ9gqP0hKNS+DMDsENM6ePXvUr1+/sL5noTeacn+46n0dXD0awlgeRdhiOQ/wnZKSEi1atEjdu3dX165dA12OVZpyfziEFy5EQFhLT5fGjGE5D/DWK6+8ol/84hcqKSlRz549tX79ekVGRga6LKt4e2FUTVg8C20sjwIAJHl3HteZM2d0/PhxFRcXa86cOSosLNTWrVvVqlWr5ik6BOTmei46qM+mTRLXF4Q3QhsAQDk5nhtOV16mS0z03AKjoacKXLhwQR06dNBrr72m8cH0lgFBjguj0FCc0wYAYa78opzLz6sqLPS0N/S9eI0xMsbo/Pnzvi8yhJXfH06qfmNf7nOIyphpA4Aw5u2Vi1999ZWWLVum4cOHKy4uTgUFBXr55Ze1detW7d+/X506dWqW+kNJTbOdXbt6AhsXRkEitAFAWPP2fKqioiL98z//s3bu3KlTp07pqquu0uDBg/Uf//Ef6tmzp7/KDXnc5xB14epRAAhj3l652LlzZ61evdr3BYU5p5OLDVA7zmkDgDDGW7oB9mB5FADCGFcuAvZgpg0AwhhXLgL2ILQBQJjjLd0AO7A8CgCQxJWLQLAjtAEAAFiA5VEAAAALENoAAAAsQGgDAACwAKENAADAAoQ2AAAACxDaAAAALEBoA0LUoUOH9NOf/lRxcXGKjo7Wvffeq2PHjgW6LACAlwhtQAgqKSnR8OHD5XA4tHHjRm3dulUXLlzQ6NGjVVZWFujyAABe4Oa6QAhat26dfvSjH+nUqVOKjo6WJJ05c0YdOnTQunXrNGzYsABXCABoLGbagBB0/vx5ORwORUVFVbS1atVKLVq00AcffBDAygAA3iK0ASHo1ltvVdu2bTV9+nSVlpaqpKRE//Zv/ya3263i4uJAlwcA8AKhDbCE2y3l5kpLl3o+ut21942Li9M777yjlStXql27drriiit0+vRp9e/fXy1a8GsPADaKCHQBAOqXkyNNnSoVFFxqS0yU5s6V0tNr3mb48OE6dOiQTpw4oYiICMXExCg+Pl4/+MEPmqdoAIBPcSECEORycqRx46TLf1MdDs/H7Ozag1tlGzdu1LBhw7R//3717NnT94UCAPyK0AYEMbdbSk6uOsNWmcPhmXHLz5eczqrPLVq0SNddd53i4uK0bds2TZ06VZMmTdKvf/1rv9cNAPA9lkeBIJaXV3tgkzyzb0ePevqlplZ97sCBA5oxY4ZOnjyp5ORk/fu//7sef/xxv9YLAPAfZtqAILZ0qTRhQv39liyRxo/3fz0AgMDhMjIgiCUk+LYfAMBezLQBQaz8nLbCwuoXIkh1n9MGAAgtzLQBQczp9NzWQ7p0tWi58s8zMwlsABAOCG1AkEtP99zWo0uXqu2JiQ2/3QcAwH4sjwKWcLs9V4kWF3vOYRs0iBk2AAgnhDYAAAALsDwKAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG2wUlZWllJTUxUdHS2Hw6HTp09X63Py5Endd999io6OVkxMjCZPnqy///3vzV8sAAA+QGiDlUpLSzVy5EjNnDmz1j733XefPv30U61fv16rVq3Sli1b9NBDDzVjlQAA+I7DGGMCXQTgrdzcXKWlpenUqVOKiYmpaN+/f7969+6tjz/+WAMGDJAkrV27VqNGjVJBQYE6d+4coIoBAPAOM20ISdu2bVNMTExFYJOkYcOGqUWLFvroo48CWBkAAN4htCEkuVwuderUqUpbRESEYmNj5XK5AlQVAADeiwh0AYAkud1SXp5UXCwlJEiDBklOZ6CrAgAgeBDaEHA5OdLUqVJBwaW2xERp7lwpPd27fcbHx+v48eNV2r777judPHlS8fHxTagWAIDAYHkUAZWTI40bVzWwSVJhoac9J8e7/d522206ffq0du7cWdG2ceNGlZWV6ZZbbmlCxQAABAZXjyJg3G4pObl6YCvncHhm3PLzqy+VulwuuVwu7dixQxkZGdqyZYvat2+vpKQkxcbGSpJ+9KMf6dixY1qwYIEuXryoBx98UAMGDNCSJUv8+40BAOAHzLQhYPLyag9skmSMdPSop9/lFixYoJSUFGVkZEiSBg8erJSUFK1YsaKiz1tvvaVevXpp6NChGjVqlO644w5lZWX5+tsAAKBZMNOGgFm6VJowof5+S5ZI48f7vx4AAIIZM20ImIQE3/YDACCUMdOGgCk/p62w0LMUerm6zmkDACDcMNOGgHE6Pbf1kDwBrbLyzzMzCWwAAEiENgRYerqUnS116VK1PTHR0+7tfdoAAAg1LI8iKPCOCAAA1I3QBgAAYAGWRwEAACxAaANC3LZt2zRkyBC1bdtW0dHRGjx4sM6ePRvosgAAjcQbxgMhbNu2bRo5cqRmzJihefPmKSIiQn/5y1/UogV/rwGAbTinDQhht956q+688049//zzgS4FANBE/LkNhKjjx4/ro48+UqdOnTRw4EBdddVV+uEPf6gPPvgg0KUBALxAaANC1FdffSVJmj17tjIyMrR27Vr1799fQ4cO1Zdffhng6gAAjUVoAyzidku5udLSpZ6PbnftfcvKyiRJDz/8sB588EGlpKTov//7v9WzZ0+9/vrrzVIvAMB3uBABsEROjjR1qlRQcKktMdHzVmA1vXNEQkKCJKl3795V2q+77jodOXLEn6UCAPyAmTbAAjk50rhxVQObJBUWetpzcqpvk5ycrM6dO+vAgQNV2r/44gt169bNj9UCAPyBq0eBIOd2S8nJ1QNbOYfDM+OWn1/9rb8yMzM1a9YsLVy4UDfeeKPeeOMNzZkzR/v27dPVV1/t99oBAL7D8igQ5PLyag9skmSMdPSop19qatXnpk2bpnPnzunxxx/XyZMn1a9fP61fv57ABgAWYqYNCHJLl0oTJtTfb8kSafx4/9cDAAgMzmkDgtz31xP4rB8AwE7MtAFBrvyctsJCz1Lo5eo6pw0AEDqYaQOCnNPpua2H5AlolZV/nplJYAOAUEdoAyyQni5lZ0tdulRtT0z0tNd0nzYAQGhheRSwiNvtuUq0uNhzDtugQcywAUC4ILQBAABYgOVRAAAACxDaAAAALEBoAwAAsAChDQAAwAKENgAAAAsQ2gAAACxAaAMAALAAoQ0AAMAChDYAAAALENoAAAAsQGgDAACwAKENAADAAoQ2AAAACxDaAAAALEBoAwAAsAChDQAAwAKENgAAAAsQ2gAAACxAaAMAALAAoQ0AAMAChDYAAAALENoAAAAsQGgDAACwAKENAADAAoQ2AAAACxDaAAAALEBoAwAAsAChDQAAwAKENgAAAAsQ2gAAACxAaAMAALAAoQ0AAMAChDYAAAALENoAAAAsQGgDAACwAKENAADAAoQ2AAAACxDaAAAALEBoAwAAsAChDQAAwAKENgAAAAsQ2gAAACxAaAMAALAAoQ0AAMAChDYAAAALENoAAAAsQGgDAACwAKENAADAAoQ2AAAACxDaAAAALEBoAwAAsAChDQAAwAKENgAAAAsQ2gAAACxAaAMAALAAoQ0AAMAChDYAAAALENoAAAAsQGgDAACwAKENAADAAoQ2AAAACxDaAAAALEBoAwAAsAChDQAAwAKENgBBKSsrS6mpqYqOjpbD4dDp06er9XnxxRc1cOBAtWnTRjExMc1eIwA0J0IbgKBUWlqqkSNHaubMmbX2uXDhgu655x49+uijzVgZAASGwxhjAl0EANQmNzdXaWlpOnXqVK2zaYsXL9a0adNqnI0DgFDBTBsAAIAFCG0AAAAWiAh0AQDCg9st5eVJxcVSQoI0aJDkdAa6KgCwB6ENgN/l5EhTp0oFBZfaEhOluXOl9PTA1QUANmF5FIBf5eRI48ZVDWySVFjoac/JCUxdAGAbZtoA+I3b7Zlhq+kadWMkh0OaNk0aM6b6UqnL5ZLL5dLBgwclSXv37lX79u2VlJSk2NhYSdKRI0d08uRJHTlyRG63W7t375Yk9ejRQ+3atfPjdwYAzY9bfgDwm9xcKS2t/n6bNkmpqVXbZs+ereeee65a30WLFmnSpEmSpEmTJumNN96oYX+blHr5DgHAcoQ2AH6zdKk0YUL9/ZYskcaP9389AGAzzmkD4DcJCb7tBwDhjJk2AH7jdkvJyZ6LDmo60jgcnqtI8/O5/QcA1IeZNgB+43R6busheQJaZeWfZ2YS2ACgIQhtAPwqPV3Kzpa6dKnanpjoaec+bQDQMCyPAmgWvCMCADQNoQ0AAMACLI8CAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABQhtQIC5XC79/Oc/V3x8vNq2bav+/fvr3XffDXRZAIAgQ2gDAmzixIk6cOCAVqxYob179yo9PV333nuvdu3aFejSAABBhNAGBNiHH36oxx57TDfffLN+8IMf6JlnnlFMTIx27twZ6NIAAEGE0AYE2MCBA7Vs2TKdPHlSZWVlevvtt3Xu3DmlpqYGujQAQBCJCHQBQLj74x//qH/8x39Ux44dFRERoTZt2mj58uXq0aNHoEsDAAQRQhvgY263lJcnFRdLCQnSoEGS01l7/2effVanT5/W+++/ryuvvFLvvfee7r33XuXl5alPnz7NVzgAIKg5jDEm0EUAoSInR5o6VSoouNSWmCjNnSulp1fvf+jQIfXo0UP79u3T9ddfX9E+bNgw9ejRQwsWLGiGqgEANuCcNsBHcnKkceOqBjZJKiz0tOfkVN+mtLRUktSiRdVfRafTqbKyMn+VCgCwEDNtgA+43VJycvXAVs7h8My45edXXSq9ePGievfurYSEBM2ZM0cdO3bUe++9p6eeekqrVq3SqFGjmqV+AEDwY6YN8IG8vNoDmyQZIx096ulXWcuWLbV69WrFxcVp9OjR6tu3r/73f/9Xb7zxBoENAFAFFyIAPlBc7H2/a665hndAAADUi5k2wAcSEnzbDwCAy3FOG+AD5ee0FRZ6lkIvV9s5bQAANBQzbYAPOJ2e23pInoBWWfnnmZkENgCA9whtgI+kp0vZ2VKXLlXbExM97TXdpw0AgIZieRTwsca+IwIAAA1BaAMAALAAy6MAAAAWILQBAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAASVJWVpZSU1MVHR0th8Oh06dPB7okAJUQ2gAAkqTS0lKNHDlSM2fODHQpAGrgMMaYQBcBAAgeubm5SktL06lTpxQTExPocgB8j5k2AADqce7cOU2ZMkUdO3ZUu3btdPfdd+vYsWOBLgthhtAGAEA9Hn/8ca1cuVLvvPOONm/erKKiIqWnpwe6LISZiEAXAADwD7dbysuTioulhARp0CDJ6Qx0VfY5c+aMFi5cqCVLlmjIkCGSpEWLFum6667T9u3bdeuttwa4QoQLZtoAIATl5EjJyVJamjRhgudjcrKnHY2zc+dOXbx4UcOGDato69Wrl5KSkrRt27YAVoZwQ2gDgBCTkyONGycVFFRtLyz0tBPcGsflcikyMrLaRRlXXXWVXC5XYIpCWGJ5FABCiNstTZ0q1XRfAGMkh0OaNk0aM6b6UqnL5ZLL5dLBgwclSXv37lX79u2VlJSk2NhY/xffjFg6ho2YaQOAEJKXV32GrTJjpKNHPf0ut2DBAqWkpCgjI0OSNHjwYKWkpGjFihV+qjYwGrt0HB8frwsXLlS72fCxY8cUHx/v73KBCoQ2AAghxcXe95s9e7aMMdUekyZN8mmNgeTN0vFNN92kli1basOGDRVtBw4c0JEjR3Tbbbf5uWLgEm6uCwAhJDfXM3NUn02bpNRUf1cTXNxuz4xabTORDoeUmCjl51dfKn300Ue1evVqLV68WNHR0XrsscckSR9++KF/iwYqIbQBQAgpDyaFhTWf11ZXMAl1TQm0586d05NPPqmlS5fq/PnzGjFihF555RWWR9GsWB4FgBDidEpz53r+7XBUfa7888zM8AtsUtOWjlu1aqX58+fr5MmTKikpUU5ODoENzY7QBgAhJj1dys6WunSp2p6Y6GkP1xv5JyT4th/Q3FgeBYAQxW0tqmLpGLYjtAEAwkb51aNS1eBWvnQczjORCH4sjwIAwgZLx7AZM20AgLDD0jFsRGgDAACwAMujAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABQhtAAAAFiC0AQAAWIDQBgAAYAFCGwAAgAUIbQAAABYgtAEAAFiA0AYAAGABQhsAAIAFCG0AAAAWILQBAABYgNAGAABgAUIbAACABf4fmsb2QFyZS0oAAAAASUVORK5CYII=\n" | |
}, | |
"metadata": {} | |
} | |
], | |
"source": [ | |
"####################################################################################\n", | |
"# Creating a ramdom instance of cities in the plane\n", | |
"####################################################################################\n", | |
"\n", | |
"width = 2000\n", | |
"height = 2000\n", | |
"seed = 5372 #157 #22 #3596\n", | |
"n_cities = 15 #18 #18 #20\n", | |
"\n", | |
"C_d, ran_points = random_cities(\n", | |
" n_cities,\n", | |
" width=width,\n", | |
" height=height,\n", | |
" seed=seed)\n", | |
"\n", | |
"fig, ax = plt.subplots(figsize=(12,6))\n", | |
"plot_situation(ran_points)\n", | |
"plt.show()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 32, | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 1000 | |
}, | |
"id": "Ny5wDC3HLeUM", | |
"outputId": "5b715da3-301c-4f73-e7ba-c8a8dfc470fa" | |
}, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"name": "stdout", | |
"text": [ | |
"===============================================================================\n", | |
" CVXPY \n", | |
" v1.6.5 \n", | |
"===============================================================================\n", | |
"(CVXPY) Apr 24 11:32:22 PM: Your problem has 50850 variables, 153722 constraints, and 0 parameters.\n", | |
"(CVXPY) Apr 24 11:32:22 PM: It is compliant with the following grammars: DCP, DQCP\n", | |
"(CVXPY) Apr 24 11:32:22 PM: (If you need to solve this problem multiple times, but with different data, consider using parameters.)\n", | |
"(CVXPY) Apr 24 11:32:22 PM: CVXPY will first compile your problem; then, it will invoke a numerical solver to obtain a solution.\n", | |
"(CVXPY) Apr 24 11:32:22 PM: Your problem is compiled with the CPP canonicalization backend.\n", | |
"-------------------------------------------------------------------------------\n", | |
" Compilation \n", | |
"-------------------------------------------------------------------------------\n", | |
"(CVXPY) Apr 24 11:32:22 PM: Compiling problem (target solver=SCS).\n", | |
"(CVXPY) Apr 24 11:32:22 PM: Reduction chain: Dcp2Cone -> CvxAttr2Constr -> ConeMatrixStuffing -> SCS\n", | |
"(CVXPY) Apr 24 11:32:22 PM: Applying reduction Dcp2Cone\n", | |
"(CVXPY) Apr 24 11:32:22 PM: Applying reduction CvxAttr2Constr\n", | |
"(CVXPY) Apr 24 11:32:22 PM: Applying reduction ConeMatrixStuffing\n", | |
"(CVXPY) Apr 24 11:32:31 PM: Applying reduction SCS\n", | |
"(CVXPY) Apr 24 11:32:31 PM: Finished problem compilation (took 8.485e+00 seconds).\n", | |
"-------------------------------------------------------------------------------\n", | |
" Numerical solver \n", | |
"-------------------------------------------------------------------------------\n", | |
"(CVXPY) Apr 24 11:32:31 PM: Invoking solver SCS to obtain a solution.\n", | |
"------------------------------------------------------------------\n", | |
"\t SCS v3.2.7 - Splitting Conic Solver\n", | |
"\t(c) Brendan O'Donoghue, Stanford University, 2012\n", | |
"------------------------------------------------------------------\n", | |
"problem: variables n: 25650, constraints m: 128297\n", | |
"cones: \t z: primal zero / dual free vars: 946\n", | |
"\t l: linear vars: 101700\n", | |
"\t s: psd vars: 25651, ssize: 1\n", | |
"settings: eps_abs: 1.0e-08, eps_rel: 1.0e-08, eps_infeas: 1.0e-07\n", | |
"\t alpha: 1.50, scale: 1.00e-01, adaptive_scale: 1\n", | |
"\t max_iters: 400000, normalize: 1, rho_x: 1.00e-06\n", | |
"\t acceleration_lookback: 10, acceleration_interval: 10\n", | |
"lin-sys: sparse-direct-amd-qdldl\n", | |
"\t nnz(A): 283501, nnz(P): 0\n", | |
"------------------------------------------------------------------\n", | |
" iter | pri res | dua res | gap | obj | scale | time (s)\n", | |
"------------------------------------------------------------------\n", | |
" 0| 3.38e+03 1.69e+03 1.08e+09 -5.39e+08 1.00e-01 4.57e-01 \n", | |
" 250| 5.32e-02 2.44e+00 6.81e+02 5.26e+03 4.66e+01 8.21e+00 \n", | |
" 500| 3.18e-03 2.18e+00 2.70e+02 6.76e+03 1.83e+02 1.70e+01 \n", | |
" 750| 1.98e-03 1.15e+00 1.92e+02 6.87e+03 1.83e+02 2.55e+01 \n", | |
" 1000| 1.39e-03 9.17e-01 1.30e+02 6.92e+03 1.83e+02 3.32e+01 \n", | |
" 1250| 9.90e-04 5.90e-01 9.75e+01 6.95e+03 1.83e+02 4.17e+01 \n", | |
" 1500| 8.15e-04 5.17e-01 8.26e+01 6.96e+03 1.83e+02 4.91e+01 \n", | |
" 1750| 6.58e-04 5.46e-01 7.24e+01 6.97e+03 1.83e+02 5.76e+01 \n", | |
" 2000| 5.31e-04 5.74e-01 6.42e+01 6.98e+03 1.83e+02 6.60e+01 \n", | |
" 2250| 4.25e-04 5.81e-01 5.65e+01 6.99e+03 1.83e+02 7.31e+01 \n", | |
" 2500| 1.04e-01 2.35e+02 9.38e+00 7.02e+03 1.83e+02 8.12e+01 \n", | |
" 2750| 2.47e-04 5.71e-01 4.19e+01 7.00e+03 1.83e+02 8.88e+01 \n", | |
" 3000| 1.71e-04 5.48e-01 3.50e+01 7.00e+03 1.83e+02 9.67e+01 \n", | |
" 3250| 1.30e-04 5.11e-01 2.79e+01 7.00e+03 1.83e+02 1.04e+02 \n", | |
" 3500| 8.93e-05 4.54e-01 2.06e+01 7.01e+03 1.83e+02 1.11e+02 \n", | |
" 3750| 9.41e-05 2.94e-01 9.31e+00 7.01e+03 1.83e+02 1.18e+02 \n", | |
" 3925| 8.32e-09 1.02e-05 1.31e-05 7.00e+03 1.83e+02 1.23e+02 \n", | |
"------------------------------------------------------------------\n", | |
"status: solved\n", | |
"timings: total: 1.23e+02s = setup: 4.16e-01s + solve: 1.22e+02s\n", | |
"\t lin-sys: 2.10e+01s, cones: 9.40e+01s, accel: 1.52e+00s\n", | |
"------------------------------------------------------------------\n", | |
"objective = 7002.878077\n", | |
"------------------------------------------------------------------\n", | |
"-------------------------------------------------------------------------------\n", | |
" Summary \n", | |
"-------------------------------------------------------------------------------\n", | |
"(CVXPY) Apr 24 11:34:33 PM: Problem status: optimal\n", | |
"(CVXPY) Apr 24 11:34:33 PM: Optimal value: 7.003e+03\n", | |
"(CVXPY) Apr 24 11:34:33 PM: Compilation took 8.485e+00 seconds\n", | |
"(CVXPY) Apr 24 11:34:33 PM: Solver (including time spent in interface) took 1.226e+02 seconds\n" | |
] | |
}, | |
{ | |
"output_type": "display_data", | |
"data": { | |
"text/plain": [ | |
"array([[-0., 0., 0., ..., -0., -0., -0.],\n", | |
" [ 0., 0., 0., ..., 0., -0., 0.],\n", | |
" [ 0., 0., 0., ..., 0., 0., 0.],\n", | |
" ...,\n", | |
" [-0., 0., 0., ..., -0., -0., 0.],\n", | |
" [-0., -0., 0., ..., -0., -0., -0.],\n", | |
" [-0., 0., 0., ..., 0., -0., 1.]])" | |
] | |
}, | |
"metadata": {} | |
} | |
], | |
"source": [ | |
"####################################################################################\n", | |
"# Run the optimization process\n", | |
"# If you plan to use MOSEK or COPT solvers, it is recommended\n", | |
"# to use a machine that has a lot of RAM\n", | |
"####################################################################################\n", | |
"\n", | |
"orden_1, X1_1, cost_opt_1, opt_time_1 = SDP_STSP_Cajas(C_d, solver='SCS')\n" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 33, | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 646 | |
}, | |
"id": "cl_pSsnmAlb3", | |
"outputId": "e786ab45-a4f4-4978-a5e2-cc52a2685360" | |
}, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"name": "stdout", | |
"text": [ | |
"Optimization time: 131.13284873962402\n", | |
"Tour Length : 7002.878083081597\n" | |
] | |
}, | |
{ | |
"output_type": "display_data", | |
"data": { | |
"text/plain": [ | |
"<Figure size 1200x600 with 1 Axes>" | |
], | |
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA1wAAAJTCAYAAAAcxBelAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAaSFJREFUeJzt3Xd4VGX6xvF7UqmhBgiEJiCI0lGKBikKiBUEXTsWUNcCiA10LT8bu7oKsipiARuIIiiisvQiTQVCkbb0loQSEjCBJEzO74+XSe+ZycnMfD/XNVemnDnzTHIIc+d9z/M6LMuyBAAAAABwuwC7CwAAAAAAX0XgAgAAAAAPIXABAAAAgIcQuAAAAADAQwhcAAAAAOAhBC4AAAAA8BACFwAAAAB4CIELAAAAADwkqLhPcDqdSktL80QtAAAAfiM4OFiBgYF2lwHAw4ocuCzLUmxsrBISEjxYDgAAgP+oXr266tWrJ4fDYXcpADykyIHLFbbq1KmjSpUq8YsBAACghCzLUnJyso4ePSpJioiIsLkiAJ5SpMDldDozwlatWrU8XRMAAIDPq1ixoiTp6NGjqlOnDtMLAR9VpKYZrnO2KlWq5NFiAAAA/InrsxXnxwO+q1hdCplGCAAA4D58tgJ8H23hAQAAAMBDCFwAAAAA4CEELg9avny5rr/+etWvX18Oh0Pff/+93SV5hffee09NmjRRhQoV1KVLF/3222+FPufw4cO68847VatWLVWsWFFt2rTRH3/8kee248aNk8Ph0MiRI91cuX041kqmuMfaBx98oLZt2yosLExhYWHq1q2bfvnll2zbvPHGG7r00ktVtWpV1alTRzfddJN27NjhybdR5kryb1Ti36mnlOTnUdhz/OF3Skm+b/wOAFASBC4PSkpKUrt27fTee+/ZXYrXmDFjhp544gm9+OKLWr9+vdq1a6d+/fpltM3Ny8mTJ3X55ZcrODhYv/zyi7Zu3ap///vfqlGjRq5tf//9d3344Ydq27ZtiWtMS0uz7eTmkydP6q+//sp1P8da8ZXkWIuMjNS4ceO0bt06/fHHH+rdu7duvPFG/fnnnxnbLFu2TI888ojWrFmjBQsWKC0tTX379lVSUlKxayyPx1pJvm+u/ZXlv1N/UZKfR1Ge487fKfkdS2XB3cdxWf8OAOAjrCI4c+aMtXXrVuvMmTNF2bxcmTZtmlWhQgXryJEjGfcNHTrUatOmjZWQkFBmdUiyZs+eXWav560uu+wy65FHHsm47XQ6rfr161tvvPFGvs955plnrCuuuKLQfZ8+fdpq0aKFtWDBAuvKK6+0RowYUeS6Dh8+bH388cfWoEGDrLCwMGvbtm25tvHUsZaWlmbNnTvXGjx4sBUaGmpFR0cXuD3HWtGU5FjLS40aNayPP/4438ePHj1qSbKWLVtWpP2V92OtpN+3svh36o9K8vMo7nNK8julKMeSNx7HeSnt7wBv/owFoGh8foTrb3/7my688EK9/vrrkqQXX3xRCxcu1C+//KJq1aoVaR+vv/66qlSpUuDlwIEDnnwbfiE1NVXr1q3TVVddlXFfQECArrrqKq1evTrf582ZM0edO3fWkCFDVKdOHXXo0EEfffRRru0eeeQRXXvttdn2nx+n06lVq1bp+eefV8eOHRUZGanx48erRYsWmjt3ri688MJcz3HHsZbV5s2bNXr0aEVGRuruu+9WeHi4lixZonbt2hV7X8iupMdaVk6nU19//bWSkpLUrVu3fLdLTEyUJNWsWTPf/XjLsVaa75sn/p36u5L8PNxx7BekOL+3vPE4zspdvwMA+L4iLXzszRwOh1577TUNHjxY9erV08SJE7VixQo1aNBAkrR48WJt2LBBo0eP1qpVq7RkyRI999xz2fbx0EMP6ZZbbinwderXr++x9+Avjh8/LqfTqbp162a7v27dutq+fXu+z9uzZ48++OADPfHEExo7dqx+//13Pf744woJCdE999wjSfr666+1fv16/f7774XWMWXKFD355JNKSkpSnz599MADD+jaa69V48aNC3xeYcealHm8devWLc9j7cSJE/ryyy/12Wef6c8//9SAAQP0/vvv67rrrlNISEihtaNoSnqsSeZDXbdu3XT27FlVqVJFs2fPVuvWrfPcNj09XSNHjtTll1+uSy65JNfj3nasleb75u5/pyjZz6M0P8P8lPT3ljcex5J7fwcA8A+lClydO0uxse4qpWjq1ZPyOcc6X9ddd51at26t//u//9P8+fN18cUXZzzWu3dv9e7dW5LUvXt3de/ePdfza9as6Rt/mXr7bXMpTMeO0pw52e+74QZp/frCn/vEE+ZShtLT09W5c+eMv5J26NBBW7Zs0aRJk3TPPffo4MGDGjFihBYsWKAKFSoUur8a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\n" | |
}, | |
"metadata": {} | |
} | |
], | |
"source": [ | |
"####################################################################################\n", | |
"# Plot the optimal tour\n", | |
"####################################################################################\n", | |
"\n", | |
"print('Optimization time:', opt_time_1)\n", | |
"print('Tour Length :', cost_opt_1)\n", | |
"fig, ax = plt.subplots(figsize=(12,6))\n", | |
"plot_situation(ran_points, x=orden_1)\n", | |
"plt.show()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 34, | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/" | |
}, | |
"id": "0Ci_b52KUcvI", | |
"outputId": "46e7605c-d471-462d-bea0-9f5b0fb601f3" | |
}, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"name": "stdout", | |
"text": [ | |
"Optimization Time:\n", | |
"131.13284873962402 seconds \n", | |
"\n", | |
"Optimal value:\n", | |
"7002.878083081597 \n", | |
"\n", | |
"Adjacency Matrix:\n", | |
"[[ 0. 1. 0. -0. 1. 0. -0. 0. 0. 0. -0. -0. -0. -0. -0.]\n", | |
" [ 1. -0. 0. -0. 0. 0. 0. 0. 1. 0. -0. 0. 0. -0. 0.]\n", | |
" [ 0. 0. -0. 0. 0. 1. 0. 1. 0. 0. 0. 0. 0. 0. 0.]\n", | |
" [-0. -0. 0. 0. -0. -0. -0. -0. -0. -0. -0. -0. 1. 1. -0.]\n", | |
" [ 1. 0. 0. -0. 0. 0. -0. -0. -0. -0. -0. -0. -0. -0. 1.]\n", | |
" [ 0. 0. 1. -0. 0. 0. -0. 0. 0. 1. -0. 0. 0. 0. 0.]\n", | |
" [-0. 0. 0. -0. -0. -0. -0. -0. -0. -0. 1. 1. -0. -0. -0.]\n", | |
" [ 0. 0. 1. -0. -0. 0. -0. -0. 0. 0. -0. 0. 0. 1. -0.]\n", | |
" [ 0. 1. 0. -0. -0. 0. -0. 0. 0. -0. -0. 1. -0. -0. -0.]\n", | |
" [ 0. 0. 0. -0. -0. 1. -0. 0. -0. -0. 1. -0. -0. -0. -0.]\n", | |
" [-0. -0. 0. -0. -0. -0. 1. -0. -0. 1. -0. -0. -0. -0. -0.]\n", | |
" [-0. 0. 0. -0. -0. 0. 1. 0. 1. -0. -0. -0. -0. -0. -0.]\n", | |
" [-0. 0. 0. 1. -0. 0. -0. 0. -0. -0. -0. -0. 0. -0. 1.]\n", | |
" [-0. -0. 0. 1. -0. 0. -0. 1. -0. -0. -0. -0. -0. -0. -0.]\n", | |
" [-0. 0. 0. -0. 1. 0. -0. -0. -0. -0. -0. -0. 1. -0. 0.]] \n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"####################################################################################\n", | |
"# Plot the optimization time and adjacency matrix\n", | |
"####################################################################################\n", | |
"\n", | |
"print('Optimization Time:')\n", | |
"print(opt_time_1, 'seconds \\n')\n", | |
"\n", | |
"print('Optimal value:')\n", | |
"print(cost_opt_1, '\\n')\n", | |
"\n", | |
"print('Adjacency Matrix:')\n", | |
"print(np.round(X1_1, 6), '\\n')" | |
] | |
} | |
], | |
"metadata": { | |
"colab": { | |
"provenance": [] | |
}, | |
"kernelspec": { | |
"display_name": "Python 3", | |
"name": "python3" | |
}, | |
"language_info": { | |
"name": "python" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 0 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment