From 1a2da37d6104c33646f74bb4b040d2a4006876c2 Mon Sep 17 00:00:00 2001 From: Chuanbo Hua Date: Thu, 14 Dec 2023 17:36:38 +0900 Subject: [PATCH] [Notebook] add tutorial notebook for testing model on VRPLib #84 --- notebooks/tutorials/6-test-on-cvrplib.ipynb | 535 ++++++++++++++++++++ 1 file changed, 535 insertions(+) create mode 100644 notebooks/tutorials/6-test-on-cvrplib.ipynb diff --git a/notebooks/tutorials/6-test-on-cvrplib.ipynb b/notebooks/tutorials/6-test-on-cvrplib.ipynb new file mode 100644 index 00000000..6711336a --- /dev/null +++ b/notebooks/tutorials/6-test-on-cvrplib.ipynb @@ -0,0 +1,535 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Test Model on VRPLib\n", + "\n", + "In this notebook, we will test the trained model's performance on the VRPLib benchmark. We will use the trained model from the previous notebook.\n", + "\n", + "[VRPLIB](http://vrp.galgos.inf.puc-rio.br/index.php/en/) is a collection of instances related to the CVRP, which is a classic optimization challenge in the field of logistics and transportation. \n", + "\n", + "## Before we start\n", + "\n", + "To use the VRPLib, we strongly recomment to use the Python `vrplib` tool:\n", + "\n", + "[VRPLib](https://github.com/leonlan/VRPLIB) is a Python package for working with Vehicle Routing Problem (VRP) instances. This tool can help us easily load the VRPLib instances and visualize the results." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Installation\n", + "\n", + "Uncomment the following line to install the package from PyPI. Remember to choose a GPU runtime for faster training!\n", + "\n", + "> Note: You may need to restart the runtime in Colab after this\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# !pip install rl4co[graph] # include torch-geometric\n", + "\n", + "## NOTE: to install latest version from Github (may be unstable) install from source instead:\n", + "# !pip install git+https://github.com/ai4co/rl4co.git" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install the `tsplib95` package\n", + "# !pip install vrplib" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "import os\n", + "import re\n", + "import torch\n", + "import vrplib\n", + "\n", + "from rl4co.envs import TSPEnv, CVRPEnv\n", + "from rl4co.models.zoo.am import AttentionModel\n", + "from rl4co.utils.trainer import RL4COTrainer\n", + "from rl4co.models.nn.utils import get_log_likelihood\n", + "from rl4co.models.zoo import EAS, EASLay, EASEmb, ActiveSearch\n", + "\n", + "from tqdm import tqdm\n", + "from math import ceil\n", + "from einops import repeat" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load a trained model" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/cbhua/miniconda3/envs/rl4co-user/lib/python3.10/site-packages/lightning/pytorch/utilities/parsing.py:198: Attribute 'env' is an instance of `nn.Module` and is already saved during checkpointing. It is recommended to ignore them using `self.save_hyperparameters(ignore=['env'])`.\n", + "/home/cbhua/miniconda3/envs/rl4co-user/lib/python3.10/site-packages/lightning/pytorch/utilities/parsing.py:198: Attribute 'policy' is an instance of `nn.Module` and is already saved during checkpointing. It is recommended to ignore them using `self.save_hyperparameters(ignore=['policy'])`.\n", + "/home/cbhua/miniconda3/envs/rl4co-user/lib/python3.10/site-packages/lightning/pytorch/core/saving.py:177: Found keys that are not in the model state dict but in the checkpoint: ['baseline.baseline.model.encoder.init_embedding.init_embed.weight', 'baseline.baseline.model.encoder.init_embedding.init_embed.bias', 'baseline.baseline.model.encoder.init_embedding.init_embed_depot.weight', 'baseline.baseline.model.encoder.init_embedding.init_embed_depot.bias', 'baseline.baseline.model.encoder.net.layers.0.0.module.Wqkv.weight', 'baseline.baseline.model.encoder.net.layers.0.0.module.Wqkv.bias', 'baseline.baseline.model.encoder.net.layers.0.0.module.out_proj.weight', 'baseline.baseline.model.encoder.net.layers.0.0.module.out_proj.bias', 'baseline.baseline.model.encoder.net.layers.0.1.normalizer.weight', 'baseline.baseline.model.encoder.net.layers.0.1.normalizer.bias', 'baseline.baseline.model.encoder.net.layers.0.1.normalizer.running_mean', 'baseline.baseline.model.encoder.net.layers.0.1.normalizer.running_var', 'baseline.baseline.model.encoder.net.layers.0.1.normalizer.num_batches_tracked', 'baseline.baseline.model.encoder.net.layers.0.2.module.0.weight', 'baseline.baseline.model.encoder.net.layers.0.2.module.0.bias', 'baseline.baseline.model.encoder.net.layers.0.2.module.2.weight', 'baseline.baseline.model.encoder.net.layers.0.2.module.2.bias', 'baseline.baseline.model.encoder.net.layers.0.3.normalizer.weight', 'baseline.baseline.model.encoder.net.layers.0.3.normalizer.bias', 'baseline.baseline.model.encoder.net.layers.0.3.normalizer.running_mean', 'baseline.baseline.model.encoder.net.layers.0.3.normalizer.running_var', 'baseline.baseline.model.encoder.net.layers.0.3.normalizer.num_batches_tracked', 'baseline.baseline.model.encoder.net.layers.1.0.module.Wqkv.weight', 'baseline.baseline.model.encoder.net.layers.1.0.module.Wqkv.bias', 'baseline.baseline.model.encoder.net.layers.1.0.module.out_proj.weight', 'baseline.baseline.model.encoder.net.layers.1.0.module.out_proj.bias', 'baseline.baseline.model.encoder.net.layers.1.1.normalizer.weight', 'baseline.baseline.model.encoder.net.layers.1.1.normalizer.bias', 'baseline.baseline.model.encoder.net.layers.1.1.normalizer.running_mean', 'baseline.baseline.model.encoder.net.layers.1.1.normalizer.running_var', 'baseline.baseline.model.encoder.net.layers.1.1.normalizer.num_batches_tracked', 'baseline.baseline.model.encoder.net.layers.1.2.module.0.weight', 'baseline.baseline.model.encoder.net.layers.1.2.module.0.bias', 'baseline.baseline.model.encoder.net.layers.1.2.module.2.weight', 'baseline.baseline.model.encoder.net.layers.1.2.module.2.bias', 'baseline.baseline.model.encoder.net.layers.1.3.normalizer.weight', 'baseline.baseline.model.encoder.net.layers.1.3.normalizer.bias', 'baseline.baseline.model.encoder.net.layers.1.3.normalizer.running_mean', 'baseline.baseline.model.encoder.net.layers.1.3.normalizer.running_var', 'baseline.baseline.model.encoder.net.layers.1.3.normalizer.num_batches_tracked', 'baseline.baseline.model.encoder.net.layers.2.0.module.Wqkv.weight', 'baseline.baseline.model.encoder.net.layers.2.0.module.Wqkv.bias', 'baseline.baseline.model.encoder.net.layers.2.0.module.out_proj.weight', 'baseline.baseline.model.encoder.net.layers.2.0.module.out_proj.bias', 'baseline.baseline.model.encoder.net.layers.2.1.normalizer.weight', 'baseline.baseline.model.encoder.net.layers.2.1.normalizer.bias', 'baseline.baseline.model.encoder.net.layers.2.1.normalizer.running_mean', 'baseline.baseline.model.encoder.net.layers.2.1.normalizer.running_var', 'baseline.baseline.model.encoder.net.layers.2.1.normalizer.num_batches_tracked', 'baseline.baseline.model.encoder.net.layers.2.2.module.0.weight', 'baseline.baseline.model.encoder.net.layers.2.2.module.0.bias', 'baseline.baseline.model.encoder.net.layers.2.2.module.2.weight', 'baseline.baseline.model.encoder.net.layers.2.2.module.2.bias', 'baseline.baseline.model.encoder.net.layers.2.3.normalizer.weight', 'baseline.baseline.model.encoder.net.layers.2.3.normalizer.bias', 'baseline.baseline.model.encoder.net.layers.2.3.normalizer.running_mean', 'baseline.baseline.model.encoder.net.layers.2.3.normalizer.running_var', 'baseline.baseline.model.encoder.net.layers.2.3.normalizer.num_batches_tracked', 'baseline.baseline.model.decoder.context_embedding.project_context.weight', 'baseline.baseline.model.decoder.project_node_embeddings.weight', 'baseline.baseline.model.decoder.project_fixed_context.weight', 'baseline.baseline.model.decoder.logit_attention.project_out.weight']\n" + ] + } + ], + "source": [ + "# Load from checkpoint; alternatively, simply instantiate a new model\n", + "# Note the model is trained for CVRP problem\n", + "checkpoint_path = \"../cvrp-20.ckpt\" # modify the path to your checkpoint file\n", + "\n", + "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n", + "\n", + "# load checkpoint\n", + "# checkpoint = torch.load(checkpoint_path)\n", + "\n", + "lit_model = AttentionModel.load_from_checkpoint(checkpoint_path, load_baseline=False)\n", + "policy, env = lit_model.policy, lit_model.env\n", + "policy = policy.to(device)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Download vrp problems" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 0%| | 0/37 [00:00 torch.Tensor:\n", + " x, y = coord[:, 0], coord[:, 1]\n", + " x_min, x_max = x.min(), x.max()\n", + " y_min, y_max = y.min(), y.max()\n", + " \n", + " x_scaled = (x - x_min) / (x_max - x_min) \n", + " y_scaled = (y - y_min) / (y_max - y_min)\n", + " coord_scaled = torch.stack([x_scaled, y_scaled], dim=1)\n", + " return coord_scaled " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test the greedy" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Problem: A-n53-k7 Cost: 1371 Optimal Cost: 1010 \t Gap: 35.74%\n", + "Problem: A-n54-k7 Cost: 1426 Optimal Cost: 1167 \t Gap: 22.19%\n", + "Problem: A-n55-k9 Cost: 1333 Optimal Cost: 1073 \t Gap: 24.23%\n", + "Problem: A-n60-k9 Cost: 1728 Optimal Cost: 1354 \t Gap: 27.62%\n", + "Problem: A-n61-k9 Cost: 1297 Optimal Cost: 1034 \t Gap: 25.44%\n", + "Problem: A-n62-k8 Cost: 1818 Optimal Cost: 1288 \t Gap: 41.15%\n", + "Problem: A-n63-k9 Cost: 2166 Optimal Cost: 1616 \t Gap: 34.03%\n", + "Problem: A-n63-k10 Cost: 1698 Optimal Cost: 1314 \t Gap: 29.22%\n", + "Problem: A-n64-k9 Cost: 1805 Optimal Cost: 1401 \t Gap: 28.84%\n", + "Problem: A-n65-k9 Cost: 1592 Optimal Cost: 1174 \t Gap: 35.60%\n", + "Problem: A-n69-k9 Cost: 1641 Optimal Cost: 1159 \t Gap: 41.59%\n", + "Problem: A-n80-k10 Cost: 2230 Optimal Cost: 1763 \t Gap: 26.49%\n", + "Problem: B-n51-k7 Cost: 1270 Optimal Cost: 1032 \t Gap: 23.06%\n", + "Problem: B-n52-k7 Cost: 994 Optimal Cost: 747 \t Gap: 33.07%\n", + "Problem: B-n56-k7 Cost: 931 Optimal Cost: 707 \t Gap: 31.68%\n", + "Problem: B-n57-k7 Cost: 1422 Optimal Cost: 1153 \t Gap: 23.33%\n", + "Problem: B-n57-k9 Cost: 1889 Optimal Cost: 1598 \t Gap: 18.21%\n", + "Problem: B-n63-k10 Cost: 1807 Optimal Cost: 1496 \t Gap: 20.79%\n", + "Problem: B-n64-k9 Cost: 1150 Optimal Cost: 861 \t Gap: 33.57%\n", + "Problem: B-n66-k9 Cost: 1746 Optimal Cost: 1316 \t Gap: 32.67%\n", + "Problem: B-n67-k10 Cost: 1368 Optimal Cost: 1032 \t Gap: 32.56%\n", + "Problem: B-n68-k9 Cost: 1737 Optimal Cost: 1272 \t Gap: 36.56%\n", + "Problem: B-n78-k10 Cost: 1706 Optimal Cost: 1221 \t Gap: 39.72%\n", + "Problem: E-n51-k5 Cost: 690 Optimal Cost: 521 \t Gap: 32.44%\n", + "Problem: E-n76-k7 Cost: 1019 Optimal Cost: 682 \t Gap: 49.41%\n", + "Problem: E-n76-k8 Cost: 1031 Optimal Cost: 735 \t Gap: 40.27%\n", + "Problem: E-n76-k10 Cost: 1156 Optimal Cost: 830 \t Gap: 39.28%\n", + "Problem: E-n76-k14 Cost: 1335 Optimal Cost: 1021 \t Gap: 30.75%\n", + "Problem: E-n101-k8 Cost: 1265 Optimal Cost: 815 \t Gap: 55.21%\n", + "Problem: E-n101-k14 Cost: 1567 Optimal Cost: 1067 \t Gap: 46.86%\n", + "Problem: F-n72-k4 Cost: 425 Optimal Cost: 237 \t Gap: 79.32%\n", + "Problem: F-n135-k7 Cost: 4219 Optimal Cost: 1162 \t Gap: 263.08%\n", + "Problem: M-n101-k10 Cost: 1388 Optimal Cost: 820 \t Gap: 69.27%\n", + "Problem: M-n121-k7 Cost: 1746 Optimal Cost: 1034 \t Gap: 68.86%\n", + "Problem: M-n151-k12 Cost: 1906 Optimal Cost: 1015 \t Gap: 87.78%\n", + "Problem: M-n200-k16 Cost: 2509 Optimal Cost: 1274 \t Gap: 96.94%\n", + "Problem: M-n200-k17 Cost: 2339 Optimal Cost: 1275 \t Gap: 83.45%\n" + ] + } + ], + "source": [ + "for instance in instances:\n", + " problem = vrplib.read_instance(os.path.join(path_to_save, instance+'.vrp'))\n", + "\n", + " coords = torch.tensor(problem['node_coord']).float()\n", + " coords_norm = normalize_coord(coords)\n", + " demand = torch.tensor(problem['demand'][1:]).float()\n", + " capacity = problem['capacity']\n", + " n = coords.shape[0]\n", + "\n", + " # Prepare the tensordict\n", + " batch_size = 2\n", + " td = env.reset(batch_size=(batch_size,)).to(device)\n", + " td['locs'] = repeat(coords_norm, 'n d -> b n d', b=batch_size, d=2)\n", + " td['demand'] = repeat(demand, 'n -> b n', b=batch_size) / capacity\n", + " td['visited'] = torch.zeros((batch_size, 1, n), dtype=torch.uint8)\n", + " action_mask = torch.ones(batch_size, n, dtype=torch.bool)\n", + " action_mask[:, 0] = False\n", + " td['action_mask'] = action_mask\n", + "\n", + " # Get the solution from the policy\n", + " with torch.no_grad():\n", + " out = policy(td.clone(), decode_type='greedy', return_actions=True)\n", + "\n", + " # Calculate the cost on the original scale\n", + " td['locs'] = repeat(coords, 'n d -> b n d', b=batch_size, d=2)\n", + " neg_reward = env.get_reward(td, out['actions'])\n", + " cost = ceil(-1 * neg_reward[0].item())\n", + "\n", + " # Load the optimal cost\n", + " solution = vrplib.read_solution(os.path.join(path_to_save, instance+'.sol'))\n", + " optimal_cost = solution['cost']\n", + "\n", + " # Calculate the gap and print\n", + " gap = (cost - optimal_cost) / optimal_cost\n", + " print(f'Problem: {instance:<15} Cost: {cost:<10} Optimal Cost: {optimal_cost:<10}\\t Gap: {gap:.2%}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test the Augmentation" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Problem: A-n53-k7 Cost: 1123 Optimal Cost: 1010 \t Gap: 11.19%\n", + "Problem: A-n54-k7 Cost: 1305 Optimal Cost: 1167 \t Gap: 11.83%\n", + "Problem: A-n55-k9 Cost: 1199 Optimal Cost: 1073 \t Gap: 11.74%\n", + "Problem: A-n60-k9 Cost: 1534 Optimal Cost: 1354 \t Gap: 13.29%\n", + "Problem: A-n61-k9 Cost: 1187 Optimal Cost: 1034 \t Gap: 14.80%\n", + "Problem: A-n62-k8 Cost: 1474 Optimal Cost: 1288 \t Gap: 14.44%\n", + "Problem: A-n63-k9 Cost: 1820 Optimal Cost: 1616 \t Gap: 12.62%\n", + "Problem: A-n63-k10 Cost: 1505 Optimal Cost: 1314 \t Gap: 14.54%\n", + "Problem: A-n64-k9 Cost: 1582 Optimal Cost: 1401 \t Gap: 12.92%\n", + "Problem: A-n65-k9 Cost: 1332 Optimal Cost: 1174 \t Gap: 13.46%\n", + "Problem: A-n69-k9 Cost: 1305 Optimal Cost: 1159 \t Gap: 12.60%\n", + "Problem: A-n80-k10 Cost: 2044 Optimal Cost: 1763 \t Gap: 15.94%\n", + "Problem: B-n51-k7 Cost: 1073 Optimal Cost: 1032 \t Gap: 3.97%\n", + "Problem: B-n52-k7 Cost: 815 Optimal Cost: 747 \t Gap: 9.10%\n", + "Problem: B-n56-k7 Cost: 792 Optimal Cost: 707 \t Gap: 12.02%\n", + "Problem: B-n57-k7 Cost: 1219 Optimal Cost: 1153 \t Gap: 5.72%\n", + "Problem: B-n57-k9 Cost: 1744 Optimal Cost: 1598 \t Gap: 9.14%\n", + "Problem: B-n63-k10 Cost: 1611 Optimal Cost: 1496 \t Gap: 7.69%\n", + "Problem: B-n64-k9 Cost: 931 Optimal Cost: 861 \t Gap: 8.13%\n", + "Problem: B-n66-k9 Cost: 1427 Optimal Cost: 1316 \t Gap: 8.43%\n", + "Problem: B-n67-k10 Cost: 1122 Optimal Cost: 1032 \t Gap: 8.72%\n", + "Problem: B-n68-k9 Cost: 1382 Optimal Cost: 1272 \t Gap: 8.65%\n", + "Problem: B-n78-k10 Cost: 1437 Optimal Cost: 1221 \t Gap: 17.69%\n", + "Problem: E-n51-k5 Cost: 606 Optimal Cost: 521 \t Gap: 16.31%\n", + "Problem: E-n76-k7 Cost: 816 Optimal Cost: 682 \t Gap: 19.65%\n", + "Problem: E-n76-k8 Cost: 892 Optimal Cost: 735 \t Gap: 21.36%\n", + "Problem: E-n76-k10 Cost: 943 Optimal Cost: 830 \t Gap: 13.61%\n", + "Problem: E-n76-k14 Cost: 1160 Optimal Cost: 1021 \t Gap: 13.61%\n", + "Problem: E-n101-k8 Cost: 1042 Optimal Cost: 815 \t Gap: 27.85%\n", + "Problem: E-n101-k14 Cost: 1302 Optimal Cost: 1067 \t Gap: 22.02%\n", + "Problem: F-n72-k4 Cost: 286 Optimal Cost: 237 \t Gap: 20.68%\n", + "Problem: F-n135-k7 Cost: 1570 Optimal Cost: 1162 \t Gap: 35.11%\n", + "Problem: M-n101-k10 Cost: 1037 Optimal Cost: 820 \t Gap: 26.46%\n", + "Problem: M-n121-k7 Cost: 1283 Optimal Cost: 1034 \t Gap: 24.08%\n", + "Problem: M-n151-k12 Cost: 1407 Optimal Cost: 1015 \t Gap: 38.62%\n", + "Problem: M-n200-k16 Cost: 1811 Optimal Cost: 1274 \t Gap: 42.15%\n", + "Problem: M-n200-k17 Cost: 1812 Optimal Cost: 1275 \t Gap: 42.12%\n" + ] + } + ], + "source": [ + "# Import augmented utils\n", + "from rl4co.data.transforms import (\n", + " StateAugmentation as SymmetricStateAugmentation)\n", + "from rl4co.utils.ops import batchify, unbatchify\n", + "\n", + "num_augment = 100\n", + "augmentation = SymmetricStateAugmentation(env.name, num_augment=num_augment)\n", + "\n", + "for instance in instances:\n", + " problem = vrplib.read_instance(os.path.join(path_to_save, instance+'.vrp'))\n", + "\n", + " coords = torch.tensor(problem['node_coord']).float()\n", + " coords_norm = normalize_coord(coords)\n", + " demand = torch.tensor(problem['demand'][1:]).float()\n", + " capacity = problem['capacity']\n", + " n = coords.shape[0]\n", + "\n", + " # Prepare the tensordict\n", + " batch_size = 2\n", + " td = env.reset(batch_size=(batch_size,)).to(device)\n", + " td['locs'] = repeat(coords_norm, 'n d -> b n d', b=batch_size, d=2)\n", + " td['demand'] = repeat(demand, 'n -> b n', b=batch_size) / capacity\n", + " td['visited'] = torch.zeros((batch_size, 1, n), dtype=torch.uint8)\n", + " action_mask = torch.ones(batch_size, n, dtype=torch.bool)\n", + " action_mask[:, 0] = False\n", + " td['action_mask'] = action_mask\n", + " \n", + " # Augmentation\n", + " td = augmentation(td)\n", + "\n", + " # Get the solution from the policy\n", + " with torch.no_grad():\n", + " out = policy(\n", + " td.clone(), decode_type='greedy', num_starts=0, return_actions=True\n", + " )\n", + "\n", + " # Calculate the cost on the original scale\n", + " coords_repeat = repeat(coords, 'n d -> b n d', b=batch_size, d=2)\n", + " td['locs'] = batchify(coords_repeat, num_augment)\n", + " reward = env.get_reward(td, out['actions'])\n", + " reward = unbatchify(reward, num_augment)\n", + " cost = ceil(-1 * torch.max(reward).item())\n", + "\n", + " # Load the optimal cost\n", + " solution = vrplib.read_solution(os.path.join(path_to_save, instance+'.sol'))\n", + " optimal_cost = solution['cost']\n", + "\n", + " # Calculate the gap and print\n", + " gap = (cost - optimal_cost) / optimal_cost\n", + " print(f'Problem: {instance:<15} Cost: {cost:<10} Optimal Cost: {optimal_cost:<10}\\t Gap: {gap:.2%}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Test the Sampling" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Problem: A-n53-k7 Cost: 1191 Optimal Cost: 1010 \t Gap: 17.92%\n", + "Problem: A-n54-k7 Cost: 1328 Optimal Cost: 1167 \t Gap: 13.80%\n", + "Problem: A-n55-k9 Cost: 1286 Optimal Cost: 1073 \t Gap: 19.85%\n", + "Problem: A-n60-k9 Cost: 1631 Optimal Cost: 1354 \t Gap: 20.46%\n", + "Problem: A-n61-k9 Cost: 1230 Optimal Cost: 1034 \t Gap: 18.96%\n", + "Problem: A-n62-k8 Cost: 1505 Optimal Cost: 1288 \t Gap: 16.85%\n", + "Problem: A-n63-k9 Cost: 1840 Optimal Cost: 1616 \t Gap: 13.86%\n", + "Problem: A-n63-k10 Cost: 1590 Optimal Cost: 1314 \t Gap: 21.00%\n", + "Problem: A-n64-k9 Cost: 1643 Optimal Cost: 1401 \t Gap: 17.27%\n", + "Problem: A-n65-k9 Cost: 1381 Optimal Cost: 1174 \t Gap: 17.63%\n", + "Problem: A-n69-k9 Cost: 1451 Optimal Cost: 1159 \t Gap: 25.19%\n", + "Problem: A-n80-k10 Cost: 2170 Optimal Cost: 1763 \t Gap: 23.09%\n", + "Problem: B-n51-k7 Cost: 1187 Optimal Cost: 1032 \t Gap: 15.02%\n", + "Problem: B-n52-k7 Cost: 884 Optimal Cost: 747 \t Gap: 18.34%\n", + "Problem: B-n56-k7 Cost: 853 Optimal Cost: 707 \t Gap: 20.65%\n", + "Problem: B-n57-k7 Cost: 1314 Optimal Cost: 1153 \t Gap: 13.96%\n", + "Problem: B-n57-k9 Cost: 1744 Optimal Cost: 1598 \t Gap: 9.14%\n", + "Problem: B-n63-k10 Cost: 1698 Optimal Cost: 1496 \t Gap: 13.50%\n", + "Problem: B-n64-k9 Cost: 1045 Optimal Cost: 861 \t Gap: 21.37%\n", + "Problem: B-n66-k9 Cost: 1506 Optimal Cost: 1316 \t Gap: 14.44%\n", + "Problem: B-n67-k10 Cost: 1254 Optimal Cost: 1032 \t Gap: 21.51%\n", + "Problem: B-n68-k9 Cost: 1510 Optimal Cost: 1272 \t Gap: 18.71%\n", + "Problem: B-n78-k10 Cost: 1514 Optimal Cost: 1221 \t Gap: 24.00%\n", + "Problem: E-n51-k5 Cost: 613 Optimal Cost: 521 \t Gap: 17.66%\n", + "Problem: E-n76-k7 Cost: 882 Optimal Cost: 682 \t Gap: 29.33%\n", + "Problem: E-n76-k8 Cost: 952 Optimal Cost: 735 \t Gap: 29.52%\n", + "Problem: E-n76-k10 Cost: 1015 Optimal Cost: 830 \t Gap: 22.29%\n", + "Problem: E-n76-k14 Cost: 1185 Optimal Cost: 1021 \t Gap: 16.06%\n", + "Problem: E-n101-k8 Cost: 1189 Optimal Cost: 815 \t Gap: 45.89%\n", + "Problem: E-n101-k14 Cost: 1420 Optimal Cost: 1067 \t Gap: 33.08%\n", + "Problem: F-n72-k4 Cost: 344 Optimal Cost: 237 \t Gap: 45.15%\n", + "Problem: F-n135-k7 Cost: 3130 Optimal Cost: 1162 \t Gap: 169.36%\n", + "Problem: M-n101-k10 Cost: 1221 Optimal Cost: 820 \t Gap: 48.90%\n", + "Problem: M-n121-k7 Cost: 1538 Optimal Cost: 1034 \t Gap: 48.74%\n", + "Problem: M-n151-k12 Cost: 1688 Optimal Cost: 1015 \t Gap: 66.31%\n", + "Problem: M-n200-k16 Cost: 2252 Optimal Cost: 1274 \t Gap: 76.77%\n", + "Problem: M-n200-k17 Cost: 2260 Optimal Cost: 1275 \t Gap: 77.25%\n" + ] + } + ], + "source": [ + "# Parameters for sampling\n", + "num_samples = 100\n", + "softmax_temp = 0.05\n", + "\n", + "for instance in instances:\n", + " problem = vrplib.read_instance(os.path.join(path_to_save, instance+'.vrp'))\n", + "\n", + " coords = torch.tensor(problem['node_coord']).float()\n", + " coords_norm = normalize_coord(coords)\n", + " demand = torch.tensor(problem['demand'][1:]).float()\n", + " capacity = problem['capacity']\n", + " n = coords.shape[0]\n", + "\n", + " # Prepare the tensordict\n", + " batch_size = 2\n", + " td = env.reset(batch_size=(batch_size,)).to(device)\n", + " td['locs'] = repeat(coords_norm, 'n d -> b n d', b=batch_size, d=2)\n", + " td['demand'] = repeat(demand, 'n -> b n', b=batch_size) / capacity\n", + " td['visited'] = torch.zeros((batch_size, 1, n), dtype=torch.uint8)\n", + " action_mask = torch.ones(batch_size, n, dtype=torch.bool)\n", + " action_mask[:, 0] = False\n", + " td['action_mask'] = action_mask\n", + " \n", + " # Sampling\n", + " td = batchify(td, num_samples)\n", + "\n", + " # Get the solution from the policy\n", + " with torch.no_grad():\n", + " out = policy(\n", + " td.clone(), decode_type='sampling', num_starts=0, return_actions=True\n", + " )\n", + "\n", + " # Calculate the cost on the original scale\n", + " coords_repeat = repeat(coords, 'n d -> b n d', b=batch_size, d=2)\n", + " td['locs'] = batchify(coords_repeat, num_samples)\n", + " reward = env.get_reward(td, out['actions'])\n", + " reward = unbatchify(reward, num_samples)\n", + " cost = ceil(-1 * torch.max(reward).item())\n", + "\n", + " # Load the optimal cost\n", + " solution = vrplib.read_solution(os.path.join(path_to_save, instance+'.sol'))\n", + " optimal_cost = solution['cost']\n", + "\n", + " # Calculate the gap and print\n", + " gap = (cost - optimal_cost) / optimal_cost\n", + " print(f'Problem: {instance:<15} Cost: {cost:<10} Optimal Cost: {optimal_cost:<10}\\t Gap: {gap:.2%}')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "rl4co-user", + "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.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}