From 25bbe20748dba789121ac647479789ab8c4e106b Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 30 Jan 2024 16:25:46 -0600 Subject: [PATCH] Make lazy constr component compatible with Pyomo+Gurobi --- docs/guide/collectors.ipynb | 43 ++++-- docs/guide/features.ipynb | 6 +- docs/guide/primal.ipynb | 2 +- docs/guide/problems.ipynb | 124 ++++++++------- docs/guide/solvers.ipynb | 28 ++-- docs/tutorials/getting-started-gurobipy.ipynb | 136 ++++++++++------- docs/tutorials/getting-started-pyomo.ipynb | 142 ++++++++++-------- miplearn/problems/__init__.py | 25 +++ miplearn/problems/stab.py | 19 +-- miplearn/problems/tsp.py | 94 +++++++++++- miplearn/solvers/pyomo.py | 7 +- tests/components/lazy/test_mem.py | 85 ++++++----- tests/conftest.py | 9 +- tests/fixtures/gen_tsp.py | 30 +++- tests/fixtures/tsp-gp-n20-00000.h5 | Bin 0 -> 32448 bytes tests/fixtures/tsp-gp-n20-00000.mps.gz | Bin 0 -> 6149 bytes ...0-00000.pkl.gz => tsp-gp-n20-00000.pkl.gz} | Bin 1145 -> 1148 bytes tests/fixtures/tsp-gp-n20-00001.h5 | Bin 0 -> 29792 bytes tests/fixtures/tsp-gp-n20-00001.mps.gz | Bin 0 -> 5286 bytes ...0-00001.pkl.gz => tsp-gp-n20-00001.pkl.gz} | Bin 1134 -> 1137 bytes tests/fixtures/tsp-gp-n20-00002.h5 | Bin 0 -> 30720 bytes tests/fixtures/tsp-gp-n20-00002.mps.gz | Bin 0 -> 5566 bytes tests/fixtures/tsp-gp-n20-00002.pkl.gz | Bin 0 -> 1137 bytes tests/fixtures/tsp-n20-00000.h5 | Bin 50503 -> 0 bytes tests/fixtures/tsp-n20-00001.h5 | Bin 40852 -> 0 bytes tests/fixtures/tsp-n20-00001.mps.gz | Bin 5371 -> 0 bytes tests/fixtures/tsp-n20-00002.h5 | Bin 42772 -> 0 bytes tests/fixtures/tsp-n20-00002.mps.gz | Bin 5653 -> 0 bytes tests/fixtures/tsp-n20-00002.pkl.gz | Bin 1134 -> 0 bytes tests/fixtures/tsp-pyo-n20-00000.h5 | Bin 0 -> 28630 bytes tests/fixtures/tsp-pyo-n20-00000.mps.gz | Bin 0 -> 9764 bytes tests/fixtures/tsp-pyo-n20-00000.pkl.gz | Bin 0 -> 1149 bytes tests/fixtures/tsp-pyo-n20-00001.h5 | Bin 0 -> 25974 bytes tests/fixtures/tsp-pyo-n20-00001.mps.gz | Bin 0 -> 8069 bytes tests/fixtures/tsp-pyo-n20-00001.pkl.gz | Bin 0 -> 1138 bytes tests/fixtures/tsp-pyo-n20-00002.h5 | Bin 0 -> 26902 bytes tests/fixtures/tsp-pyo-n20-00002.mps.gz | Bin 0 -> 8673 bytes tests/fixtures/tsp-pyo-n20-00002.pkl.gz | Bin 0 -> 1138 bytes tests/problems/test_tsp.py | 4 +- 39 files changed, 486 insertions(+), 268 deletions(-) create mode 100644 tests/fixtures/tsp-gp-n20-00000.h5 create mode 100644 tests/fixtures/tsp-gp-n20-00000.mps.gz rename tests/fixtures/{tsp-n20-00000.pkl.gz => tsp-gp-n20-00000.pkl.gz} (78%) create mode 100644 tests/fixtures/tsp-gp-n20-00001.h5 create mode 100644 tests/fixtures/tsp-gp-n20-00001.mps.gz rename tests/fixtures/{tsp-n20-00001.pkl.gz => tsp-gp-n20-00001.pkl.gz} (56%) create mode 100644 tests/fixtures/tsp-gp-n20-00002.h5 create mode 100644 tests/fixtures/tsp-gp-n20-00002.mps.gz create mode 100644 tests/fixtures/tsp-gp-n20-00002.pkl.gz delete mode 100644 tests/fixtures/tsp-n20-00000.h5 delete mode 100644 tests/fixtures/tsp-n20-00001.h5 delete mode 100644 tests/fixtures/tsp-n20-00001.mps.gz delete mode 100644 tests/fixtures/tsp-n20-00002.h5 delete mode 100644 tests/fixtures/tsp-n20-00002.mps.gz delete mode 100644 tests/fixtures/tsp-n20-00002.pkl.gz create mode 100644 tests/fixtures/tsp-pyo-n20-00000.h5 create mode 100644 tests/fixtures/tsp-pyo-n20-00000.mps.gz create mode 100644 tests/fixtures/tsp-pyo-n20-00000.pkl.gz create mode 100644 tests/fixtures/tsp-pyo-n20-00001.h5 create mode 100644 tests/fixtures/tsp-pyo-n20-00001.mps.gz create mode 100644 tests/fixtures/tsp-pyo-n20-00001.pkl.gz create mode 100644 tests/fixtures/tsp-pyo-n20-00002.h5 create mode 100644 tests/fixtures/tsp-pyo-n20-00002.mps.gz create mode 100644 tests/fixtures/tsp-pyo-n20-00002.pkl.gz diff --git a/docs/guide/collectors.ipynb b/docs/guide/collectors.ipynb index 42ec697..a9b4bb6 100644 --- a/docs/guide/collectors.ipynb +++ b/docs/guide/collectors.ipynb @@ -38,9 +38,13 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "id": "f906fe9c", "metadata": { + "ExecuteTime": { + "end_time": "2024-01-30T22:19:30.826123021Z", + "start_time": "2024-01-30T22:19:30.766066926Z" + }, "collapsed": false, "jupyter": { "outputs_hidden": false @@ -57,18 +61,18 @@ "x4 = [[0.37454012 0.9507143 0.7319939 ]\n", " [0.5986585 0.15601864 0.15599452]\n", " [0.05808361 0.8661761 0.601115 ]]\n", - "x5 = (2, 3)\t0.68030757\n", - " (3, 2)\t0.45049927\n", - " (4, 0)\t0.013264962\n", - " (0, 2)\t0.94220173\n", - " (4, 2)\t0.5632882\n", - " (2, 1)\t0.3854165\n", - " (1, 1)\t0.015966251\n", - " (3, 0)\t0.23089382\n", - " (4, 4)\t0.24102546\n", - " (1, 3)\t0.68326354\n", - " (3, 1)\t0.6099967\n", - " (0, 3)\t0.8331949\n" + "x5 = (3, 2)\t0.6803075671195984\n", + " (2, 3)\t0.4504992663860321\n", + " (0, 4)\t0.013264961540699005\n", + " (2, 0)\t0.9422017335891724\n", + " (2, 4)\t0.5632882118225098\n", + " (1, 2)\t0.38541650772094727\n", + " (1, 1)\t0.015966251492500305\n", + " (0, 3)\t0.2308938205242157\n", + " (4, 4)\t0.24102546274662018\n", + " (3, 1)\t0.6832635402679443\n", + " (1, 3)\t0.6099966764450073\n", + " (3, 0)\t0.83319491147995\n" ] } ], @@ -182,6 +186,10 @@ "execution_count": 4, "id": "ac6f8c6f", "metadata": { + "ExecuteTime": { + "end_time": "2024-01-30T22:19:30.826707866Z", + "start_time": "2024-01-30T22:19:30.825940503Z" + }, "collapsed": false, "jupyter": { "outputs_hidden": false @@ -205,7 +213,7 @@ "\n", "from miplearn.problems.tsp import (\n", " TravelingSalesmanGenerator,\n", - " build_tsp_model,\n", + " build_tsp_model_gurobipy,\n", ")\n", "from miplearn.io import write_pkl_gz\n", "from miplearn.h5 import H5File\n", @@ -231,7 +239,7 @@ "# Solve all instances and collect basic solution information.\n", "# Process at most four instances in parallel.\n", "bc = BasicCollector()\n", - "bc.collect(glob(\"data/tsp/*.pkl.gz\"), build_tsp_model, n_jobs=4)\n", + "bc.collect(glob(\"data/tsp/*.pkl.gz\"), build_tsp_model_gurobipy, n_jobs=4)\n", "\n", "# Read and print some training data for the first instance.\n", "with H5File(\"data/tsp/00000.h5\", \"r\") as h5:\n", @@ -244,6 +252,9 @@ "execution_count": null, "id": "78f0b07a", "metadata": { + "ExecuteTime": { + "start_time": "2024-01-30T22:19:30.826179789Z" + }, "collapsed": false, "jupyter": { "outputs_hidden": false @@ -269,7 +280,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/docs/guide/features.ipynb b/docs/guide/features.ipynb index d2ae648..81c2629 100644 --- a/docs/guide/features.ipynb +++ b/docs/guide/features.ipynb @@ -51,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 1, "id": "ed9a18c8", "metadata": { "collapsed": false, @@ -204,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 2, "id": "a1bc38fe", "metadata": { "collapsed": false, @@ -326,7 +326,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/docs/guide/primal.ipynb b/docs/guide/primal.ipynb index 056992d..26464ce 100644 --- a/docs/guide/primal.ipynb +++ b/docs/guide/primal.ipynb @@ -283,7 +283,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/docs/guide/problems.ipynb b/docs/guide/problems.ipynb index 24a551e..bf7d9fb 100644 --- a/docs/guide/problems.ipynb +++ b/docs/guide/problems.ipynb @@ -108,11 +108,11 @@ "execution_count": 1, "id": "f14e560c-ef9f-4c48-8467-72d6acce5f9f", "metadata": { - "tags": [], "ExecuteTime": { "end_time": "2023-11-07T16:29:48.409419720Z", "start_time": "2023-11-07T16:29:47.824353556Z" - } + }, + "tags": [] }, "outputs": [ { @@ -131,10 +131,10 @@ "9 [ 8.57 22.77 17.06 16.25 4.14 4. 1.56 22.97 14.09 19.09] 100.79\n", "\n", "Restricted license - for non-production use only - expires 2024-10-28\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 20 rows, 110 columns and 210 nonzeros\n", "Model fingerprint: 0x1ff9913f\n", @@ -159,8 +159,8 @@ "H 0 0 2.0000000 1.27484 36.3% - 0s\n", " 0 0 1.27484 0 4 2.00000 1.27484 36.3% - 0s\n", "\n", - "Explored 1 nodes (38 simplex iterations) in 0.02 seconds (0.00 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Explored 1 nodes (38 simplex iterations) in 0.01 seconds (0.00 work units)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", "Solution count 3: 2 4 5 \n", "\n", @@ -323,10 +323,10 @@ "capacities\n", " [1310. 988. 1004. 1269. 1007.]\n", "\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 5 rows, 10 columns and 50 nonzeros\n", "Model fingerprint: 0xaf3ac15e\n", @@ -354,7 +354,7 @@ " Cover: 1\n", "\n", "Explored 1 nodes (4 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", "Solution count 2: -1279 -804 \n", "No other solutions better than -1279\n", @@ -498,10 +498,10 @@ "demands = [6.12 1.39 2.92 3.66 4.56 7.85 2. 5.14 5.92 0.46]\n", "capacities = [151.89 42.63 16.26 237.22 241.41 202.1 76.15 24.42 171.06 110.04]\n", "\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 21 rows, 110 columns and 220 nonzeros\n", "Model fingerprint: 0x8d8d9346\n", @@ -535,8 +535,8 @@ " 0 0 86.06884 0 15 93.92000 86.06884 8.36% - 0s\n", "* 0 0 0 91.2300000 91.23000 0.00% - 0s\n", "\n", - "Explored 1 nodes (70 simplex iterations) in 0.02 seconds (0.00 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Explored 1 nodes (70 simplex iterations) in 0.07 seconds (0.00 work units)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", "Solution count 10: 91.23 93.92 93.98 ... 368.79\n", "\n", @@ -670,10 +670,10 @@ "costs [1044.58 850.13 1014.5 944.83 697.9 971.87 213.49 220.98 70.23\n", " 425.33]\n", "\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 5 rows, 10 columns and 28 nonzeros\n", "Model fingerprint: 0xe5c2d4fa\n", @@ -688,8 +688,8 @@ "Presolve time: 0.00s\n", "Presolve: All rows and columns removed\n", "\n", - "Explored 0 nodes (0 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 1 (of 12 available processors)\n", + "Explored 0 nodes (0 simplex iterations) in 0.00 seconds (0.00 work units)\n", + "Thread count was 1 (of 20 available processors)\n", "\n", "Solution count 1: 213.49 \n", "\n", @@ -786,10 +786,13 @@ "execution_count": 5, "id": "cc797da7", "metadata": { - "collapsed": false, "ExecuteTime": { "end_time": "2023-11-07T16:29:48.806917868Z", "start_time": "2023-11-07T16:29:48.781619530Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false } }, "outputs": [ @@ -806,10 +809,10 @@ "costs [1044.58 850.13 1014.5 944.83 697.9 971.87 213.49 220.98 70.23\n", " 425.33]\n", "\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 5 rows, 10 columns and 28 nonzeros\n", "Model fingerprint: 0x4ee91388\n", @@ -824,8 +827,9 @@ "Presolve time: 0.00s\n", "Presolve: All rows and columns removed\n", "\n", - "Explored 0 nodes (0 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 1 (of 12 available processors)\n", + "Explored 0 nodes (0 simplex iterations) in 0.00 seconds (0.00 work units)\n", + "Thread count was 1 (of 20 available processors)\n", + "\n", "Solution count 2: -1986.37 -1265.56 \n", "No other solutions better than -1986.37\n", "\n", @@ -930,10 +934,10 @@ "weights[1] [ 2.06 96.99 83.24 21.23 18.18 18.34 30.42 52.48 43.19 29.12]\n", "\n", "Set parameter PreCrush to value 1\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 15 rows, 10 columns and 30 nonzeros\n", "Model fingerprint: 0x3240ea4a\n", @@ -957,7 +961,7 @@ " 0 0 infeasible 0 -219.14000 -219.14000 0.00% - 0s\n", "\n", "Explored 1 nodes (5 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", "Solution count 1: -219.14 \n", "No other solutions better than -219.14\n", @@ -965,7 +969,7 @@ "Optimal solution found (tolerance 1.00e-04)\n", "Best objective -2.191400000000e+02, best bound -2.191400000000e+02, gap 0.0000%\n", "\n", - "User-callback calls 300, time in user-callback 0.00 sec\n" + "User-callback calls 299, time in user-callback 0.00 sec\n" ] } ], @@ -975,7 +979,7 @@ "from scipy.stats import uniform, randint\n", "from miplearn.problems.stab import (\n", " MaxWeightStableSetGenerator,\n", - " build_stab_model,\n", + " build_stab_model_gurobipy,\n", ")\n", "\n", "# Set random seed to make example reproducible\n", @@ -998,7 +1002,7 @@ "print()\n", "\n", "# Load and optimize the first instance\n", - "model = build_stab_model(data[0])\n", + "model = build_stab_model_gurobipy(data[0])\n", "model.optimize()" ] }, @@ -1071,10 +1075,13 @@ "execution_count": 7, "id": "9d0c56c6", "metadata": { - "collapsed": false, "ExecuteTime": { "end_time": "2023-11-07T16:29:48.958833448Z", "start_time": "2023-11-07T16:29:48.898121017Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false } }, "outputs": [ @@ -1107,10 +1114,10 @@ "\n", "Set parameter PreCrush to value 1\n", "Set parameter LazyConstraints to value 1\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 10 rows, 45 columns and 90 nonzeros\n", "Model fingerprint: 0x719675e5\n", @@ -1135,7 +1142,7 @@ " Lazy constraints: 3\n", "\n", "Explored 1 nodes (17 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", "Solution count 1: 2921 \n", "\n", @@ -1150,7 +1157,10 @@ "import random\n", "import numpy as np\n", "from scipy.stats import uniform, randint\n", - "from miplearn.problems.tsp import TravelingSalesmanGenerator, build_tsp_model\n", + "from miplearn.problems.tsp import (\n", + " TravelingSalesmanGenerator,\n", + " build_tsp_model_gurobipy,\n", + ")\n", "\n", "# Set random seed to make example reproducible\n", "random.seed(42)\n", @@ -1173,7 +1183,7 @@ "print()\n", "\n", "# Load and optimize the first instance\n", - "model = build_tsp_model(data[0])\n", + "model = build_tsp_model_gurobipy(data[0])\n", "model.optimize()" ] }, @@ -1283,10 +1293,13 @@ "execution_count": 8, "id": "6217da7c", "metadata": { - "collapsed": false, "ExecuteTime": { "end_time": "2023-11-07T16:29:49.061613905Z", "start_time": "2023-11-07T16:29:48.941857719Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false } }, "outputs": [ @@ -1320,10 +1333,10 @@ " 828.28 775.18 834.99 959.76 865.72 1193.52 1058.92 985.19 893.92\n", " 962.16 781.88 723.15 639.04 602.4 787.02]\n", "\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 578 rows, 360 columns and 2128 nonzeros\n", "Model fingerprint: 0x4dc1c661\n", @@ -1361,8 +1374,8 @@ " RLT: 1\n", " Relax-and-lift: 7\n", "\n", - "Explored 1 nodes (234 simplex iterations) in 0.04 seconds (0.02 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Explored 1 nodes (234 simplex iterations) in 0.03 seconds (0.02 work units)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", "Solution count 5: 364722 368600 374044 ... 440662\n", "\n", @@ -1487,10 +1500,10 @@ "weights[0] [37.45 95.07 73.2 59.87 15.6 15.6 5.81 86.62 60.11 70.81]\n", "weights[1] [ 2.06 96.99 83.24 21.23 18.18 18.34 30.42 52.48 43.19 29.12]\n", "\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 15 rows, 10 columns and 30 nonzeros\n", "Model fingerprint: 0x2d2d1390\n", @@ -1514,7 +1527,7 @@ " 0 0 infeasible 0 301.00000 301.00000 0.00% - 0s\n", "\n", "Explored 1 nodes (8 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", "Solution count 1: 301 \n", "\n", @@ -1558,13 +1571,16 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "9f12e91f", "metadata": { - "collapsed": false, "ExecuteTime": { "end_time": "2023-11-07T16:29:49.075852252Z", "start_time": "2023-11-07T16:29:49.050243601Z" + }, + "collapsed": false, + "jupyter": { + "outputs_hidden": false } }, "outputs": [], @@ -1587,7 +1603,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/docs/guide/solvers.ipynb b/docs/guide/solvers.ipynb index e19682d..d89824a 100644 --- a/docs/guide/solvers.ipynb +++ b/docs/guide/solvers.ipynb @@ -57,7 +57,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "id": "92b09b98", "metadata": { "collapsed": false, @@ -70,10 +70,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Restricted license - for non-production use only - expires 2024-10-28\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n", - "Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 10 rows, 45 columns and 90 nonzeros\n", "Model fingerprint: 0x6ddcd141\n", @@ -91,11 +92,12 @@ "\n", "Solved in 15 iterations and 0.00 seconds (0.00 work units)\n", "Optimal objective 2.761000000e+03\n", + "Set parameter PreCrush to value 1\n", "Set parameter LazyConstraints to value 1\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n", - "Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 10 rows, 45 columns and 90 nonzeros\n", "Model fingerprint: 0x74ca3d0a\n", @@ -125,7 +127,7 @@ " Lazy constraints: 3\n", "\n", "Explored 1 nodes (16 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 32 (of 32 available processors)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", "Solution count 1: 2796 \n", "\n", @@ -141,7 +143,7 @@ "{'WS: Count': 1, 'WS: Number of variables set': 41.0}" ] }, - "execution_count": 3, + "execution_count": 1, "metadata": {}, "output_type": "execute_result" } @@ -162,7 +164,7 @@ "from miplearn.io import write_pkl_gz\n", "from miplearn.problems.tsp import (\n", " TravelingSalesmanGenerator,\n", - " build_tsp_model,\n", + " build_tsp_model_gurobipy,\n", ")\n", "from miplearn.solvers.learning import LearningSolver\n", "\n", @@ -189,7 +191,7 @@ "\n", "# Collect training data\n", "bc = BasicCollector()\n", - "bc.collect(train_data, build_tsp_model, n_jobs=4)\n", + "bc.collect(train_data, build_tsp_model_gurobipy, n_jobs=4)\n", "\n", "# Build learning solver\n", "solver = LearningSolver(\n", @@ -211,7 +213,7 @@ "solver.fit(train_data)\n", "\n", "# Solve a test instance\n", - "solver.optimize(test_data[0], build_tsp_model)" + "solver.optimize(test_data[0], build_tsp_model_gurobipy)" ] }, { @@ -239,7 +241,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/docs/tutorials/getting-started-gurobipy.ipynb b/docs/tutorials/getting-started-gurobipy.ipynb index 36f0d9d..39f757b 100644 --- a/docs/tutorials/getting-started-gurobipy.ipynb +++ b/docs/tutorials/getting-started-gurobipy.ipynb @@ -127,7 +127,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "id": "22a67170-10b4-43d3-8708-014d91141e73", "metadata": { "ExecuteTime": { @@ -163,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "id": "2f67032f-0d74-4317-b45c-19da0ec859e9", "metadata": { "ExecuteTime": { @@ -207,7 +207,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "id": "2a896f47", "metadata": { "ExecuteTime": { @@ -221,10 +221,10 @@ "output_type": "stream", "text": [ "Restricted license - for non-production use only - expires 2024-10-28\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 7 rows, 6 columns and 15 nonzeros\n", "Model fingerprint: 0x58dfdd53\n", @@ -250,7 +250,7 @@ "* 0 0 0 1320.0000000 1320.00000 0.00% - 0s\n", "\n", "Explored 1 nodes (5 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", "Solution count 2: 1320 1400 \n", "\n", @@ -315,7 +315,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "id": "5eb09fab", "metadata": { "ExecuteTime": { @@ -361,7 +361,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "id": "6156752c", "metadata": { "ExecuteTime": { @@ -388,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "id": "7623f002", "metadata": { "ExecuteTime": { @@ -429,7 +429,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "id": "435f7bf8-4b09-4889-b1ec-b7b56e7d8ed2", "metadata": { "ExecuteTime": { @@ -467,7 +467,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "id": "9d13dd50-3dcf-4673-a757-6f44dcc0dedf", "metadata": { "ExecuteTime": { @@ -480,10 +480,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n", "Model fingerprint: 0xa8b70287\n", @@ -493,7 +493,7 @@ " Bounds range [1e+00, 1e+00]\n", " RHS range [3e+08, 3e+08]\n", "Presolve removed 1000 rows and 500 columns\n", - "Presolve time: 0.00s\n", + "Presolve time: 0.01s\n", "Presolved: 1 rows, 500 columns, 500 nonzeros\n", "\n", "Iteration Objective Primal Inf. Dual Inf. Time\n", @@ -502,13 +502,13 @@ "\n", "Solved in 1 iterations and 0.01 seconds (0.00 work units)\n", "Optimal objective 8.290621916e+09\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n", - "Model fingerprint: 0x4ccd7ae3\n", + "Model fingerprint: 0xcf27855a\n", "Variable types: 500 continuous, 500 integer (500 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 2e+06]\n", @@ -516,11 +516,9 @@ " Bounds range [1e+00, 1e+00]\n", " RHS range [3e+08, 3e+08]\n", "\n", - "User MIP start produced solution with objective 8.30129e+09 (0.01s)\n", - "User MIP start produced solution with objective 8.29184e+09 (0.01s)\n", - "User MIP start produced solution with objective 8.29146e+09 (0.01s)\n", - "User MIP start produced solution with objective 8.29146e+09 (0.01s)\n", - "Loaded user MIP start with objective 8.29146e+09\n", + "User MIP start produced solution with objective 8.29153e+09 (0.01s)\n", + "User MIP start produced solution with objective 8.29153e+09 (0.01s)\n", + "Loaded user MIP start with objective 8.29153e+09\n", "\n", "Presolve time: 0.00s\n", "Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n", @@ -532,19 +530,32 @@ " Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n", "\n", " 0 0 8.2906e+09 0 1 8.2915e+09 8.2906e+09 0.01% - 0s\n", + " 0 0 8.2907e+09 0 3 8.2915e+09 8.2907e+09 0.01% - 0s\n", + " 0 0 8.2907e+09 0 1 8.2915e+09 8.2907e+09 0.01% - 0s\n", + " 0 0 8.2907e+09 0 2 8.2915e+09 8.2907e+09 0.01% - 0s\n", "\n", "Cutting planes:\n", - " Cover: 1\n", + " Gomory: 1\n", " Flow cover: 2\n", "\n", - "Explored 1 nodes (512 simplex iterations) in 0.07 seconds (0.01 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Explored 1 nodes (565 simplex iterations) in 0.03 seconds (0.01 work units)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", - "Solution count 3: 8.29146e+09 8.29184e+09 8.30129e+09 \n", + "Solution count 1: 8.29153e+09 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", - "Best objective 8.291459497797e+09, best bound 8.290645029670e+09, gap 0.0098%\n" + "Best objective 8.291528276179e+09, best bound 8.290733258025e+09, gap 0.0096%\n" ] + }, + { + "data": { + "text/plain": [ + "{'WS: Count': 1, 'WS: Number of variables set': 482.0}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -565,7 +576,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "id": "2ff391ed-e855-4228-aa09-a7641d8c2893", "metadata": { "ExecuteTime": { @@ -578,10 +589,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n", "Model fingerprint: 0xa8b70287\n", @@ -600,10 +611,10 @@ "\n", "Solved in 1 iterations and 0.01 seconds (0.00 work units)\n", "Optimal objective 8.290621916e+09\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n", "Model fingerprint: 0x4cbbf7c7\n", @@ -641,14 +652,24 @@ " Gomory: 2\n", " MIR: 1\n", "\n", - "Explored 1 nodes (1031 simplex iterations) in 0.07 seconds (0.03 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Explored 1 nodes (1031 simplex iterations) in 0.15 seconds (0.03 work units)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", "Solution count 4: 8.29147e+09 8.29398e+09 8.29827e+09 9.75713e+09 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 8.291465302389e+09, best bound 8.290781665333e+09, gap 0.0082%\n" ] + }, + { + "data": { + "text/plain": [ + "{}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -679,7 +700,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "id": "67a6cd18", "metadata": { "ExecuteTime": { @@ -692,10 +713,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n", "Model fingerprint: 0x19042f12\n", @@ -714,13 +735,13 @@ "\n", "Solved in 1 iterations and 0.01 seconds (0.00 work units)\n", "Optimal objective 8.253596777e+09\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n", - "Model fingerprint: 0x8ee64638\n", + "Model fingerprint: 0xf97cde91\n", "Variable types: 500 continuous, 500 integer (500 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 2e+06]\n", @@ -728,13 +749,16 @@ " Bounds range [1e+00, 1e+00]\n", " RHS range [3e+08, 3e+08]\n", "\n", - "User MIP start produced solution with objective 8.25814e+09 (0.01s)\n", + "User MIP start produced solution with objective 8.25814e+09 (0.00s)\n", "User MIP start produced solution with objective 8.25512e+09 (0.01s)\n", - "User MIP start produced solution with objective 8.25459e+09 (0.04s)\n", - "User MIP start produced solution with objective 8.25459e+09 (0.04s)\n", + "User MIP start produced solution with objective 8.25483e+09 (0.01s)\n", + "User MIP start produced solution with objective 8.25483e+09 (0.01s)\n", + "User MIP start produced solution with objective 8.25483e+09 (0.01s)\n", + "User MIP start produced solution with objective 8.25459e+09 (0.01s)\n", + "User MIP start produced solution with objective 8.25459e+09 (0.01s)\n", "Loaded user MIP start with objective 8.25459e+09\n", "\n", - "Presolve time: 0.01s\n", + "Presolve time: 0.00s\n", "Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n", "Variable types: 500 continuous, 500 integer (500 binary)\n", "\n", @@ -758,16 +782,16 @@ " StrongCG: 1\n", " Flow cover: 1\n", "\n", - "Explored 1 nodes (575 simplex iterations) in 0.12 seconds (0.01 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Explored 1 nodes (575 simplex iterations) in 0.05 seconds (0.01 work units)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", - "Solution count 3: 8.25459e+09 8.25512e+09 8.25814e+09 \n", + "Solution count 4: 8.25459e+09 8.25483e+09 8.25512e+09 8.25814e+09 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 8.254590409970e+09, best bound 8.253768093811e+09, gap 0.0100%\n", "obj = 8254590409.969726\n", "x = [1.0, 1.0, 0.0]\n", - "y = [935662.0949263407, 1604270.0218116897, 0.0]\n" + "y = [935662.0949262811, 1604270.0218116897, 0.0]\n" ] } ], @@ -805,7 +829,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/docs/tutorials/getting-started-pyomo.ipynb b/docs/tutorials/getting-started-pyomo.ipynb index 77e2c28..2618f74 100644 --- a/docs/tutorials/getting-started-pyomo.ipynb +++ b/docs/tutorials/getting-started-pyomo.ipynb @@ -127,7 +127,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "id": "22a67170-10b4-43d3-8708-014d91141e73", "metadata": { "ExecuteTime": { @@ -163,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "id": "2f67032f-0d74-4317-b45c-19da0ec859e9", "metadata": { "ExecuteTime": { @@ -213,7 +213,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "id": "2a896f47", "metadata": { "ExecuteTime": { @@ -228,10 +228,10 @@ "text": [ "Restricted license - for non-production use only - expires 2024-10-28\n", "Set parameter QCPDual to value 1\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 7 rows, 6 columns and 15 nonzeros\n", "Model fingerprint: 0x15c7a953\n", @@ -257,7 +257,7 @@ "* 0 0 0 1320.0000000 1320.00000 0.00% - 0s\n", "\n", "Explored 1 nodes (5 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", "Solution count 2: 1320 1400 \n", "\n", @@ -324,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "id": "5eb09fab", "metadata": { "ExecuteTime": { @@ -370,7 +370,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "id": "6156752c", "metadata": { "ExecuteTime": { @@ -397,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "id": "7623f002", "metadata": { "ExecuteTime": { @@ -438,7 +438,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "id": "435f7bf8-4b09-4889-b1ec-b7b56e7d8ed2", "metadata": { "ExecuteTime": { @@ -476,7 +476,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "id": "9d13dd50-3dcf-4673-a757-6f44dcc0dedf", "metadata": { "ExecuteTime": { @@ -490,10 +490,10 @@ "output_type": "stream", "text": [ "Set parameter QCPDual to value 1\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n", "Model fingerprint: 0x5e67c6ee\n", @@ -513,13 +513,13 @@ "Solved in 1 iterations and 0.01 seconds (0.00 work units)\n", "Optimal objective 8.290621916e+09\n", "Set parameter QCPDual to value 1\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n", - "Model fingerprint: 0xa4a7961e\n", + "Model fingerprint: 0x4a7cfe2b\n", "Variable types: 500 continuous, 500 integer (500 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 2e+06]\n", @@ -527,37 +527,48 @@ " Bounds range [1e+00, 1e+00]\n", " RHS range [3e+08, 3e+08]\n", "\n", - "User MIP start produced solution with objective 8.30129e+09 (0.01s)\n", - "User MIP start produced solution with objective 8.29184e+09 (0.01s)\n", - "User MIP start produced solution with objective 8.29146e+09 (0.01s)\n", - "User MIP start produced solution with objective 8.29146e+09 (0.02s)\n", - "Loaded user MIP start with objective 8.29146e+09\n", + "User MIP start produced solution with objective 8.29153e+09 (0.01s)\n", + "User MIP start produced solution with objective 8.29153e+09 (0.01s)\n", + "Loaded user MIP start with objective 8.29153e+09\n", "\n", - "Presolve time: 0.01s\n", + "Presolve time: 0.00s\n", "Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n", "Variable types: 500 continuous, 500 integer (500 binary)\n", "\n", - "Root relaxation: objective 8.290622e+09, 512 iterations, 0.01 seconds (0.00 work units)\n", + "Root relaxation: objective 8.290622e+09, 512 iterations, 0.00 seconds (0.00 work units)\n", "\n", " Nodes | Current Node | Objective Bounds | Work\n", " Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n", "\n", " 0 0 8.2906e+09 0 1 8.2915e+09 8.2906e+09 0.01% - 0s\n", + " 0 0 8.2907e+09 0 3 8.2915e+09 8.2907e+09 0.01% - 0s\n", + " 0 0 8.2907e+09 0 1 8.2915e+09 8.2907e+09 0.01% - 0s\n", + " 0 0 8.2907e+09 0 2 8.2915e+09 8.2907e+09 0.01% - 0s\n", "\n", "Cutting planes:\n", - " Cover: 1\n", + " Gomory: 1\n", " Flow cover: 2\n", "\n", - "Explored 1 nodes (512 simplex iterations) in 0.09 seconds (0.01 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Explored 1 nodes (565 simplex iterations) in 0.04 seconds (0.01 work units)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", - "Solution count 3: 8.29146e+09 8.29184e+09 8.30129e+09 \n", + "Solution count 1: 8.29153e+09 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", - "Best objective 8.291459497797e+09, best bound 8.290645029670e+09, gap 0.0098%\n", + "Best objective 8.291528276179e+09, best bound 8.290733258025e+09, gap 0.0096%\n", "WARNING: Cannot get reduced costs for MIP.\n", "WARNING: Cannot get duals for MIP.\n" ] + }, + { + "data": { + "text/plain": [ + "{}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -578,7 +589,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "id": "2ff391ed-e855-4228-aa09-a7641d8c2893", "metadata": { "ExecuteTime": { @@ -592,10 +603,10 @@ "output_type": "stream", "text": [ "Set parameter QCPDual to value 1\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n", "Model fingerprint: 0x5e67c6ee\n", @@ -605,7 +616,7 @@ " Bounds range [1e+00, 1e+00]\n", " RHS range [3e+08, 3e+08]\n", "Presolve removed 1000 rows and 500 columns\n", - "Presolve time: 0.01s\n", + "Presolve time: 0.00s\n", "Presolved: 1 rows, 500 columns, 500 nonzeros\n", "\n", "Iteration Objective Primal Inf. Dual Inf. Time\n", @@ -615,10 +626,10 @@ "Solved in 1 iterations and 0.01 seconds (0.00 work units)\n", "Optimal objective 8.290621916e+09\n", "Set parameter QCPDual to value 1\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n", "Model fingerprint: 0x8a0f9587\n", @@ -656,8 +667,8 @@ " Gomory: 2\n", " MIR: 1\n", "\n", - "Explored 1 nodes (1025 simplex iterations) in 0.08 seconds (0.03 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Explored 1 nodes (1025 simplex iterations) in 0.12 seconds (0.03 work units)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", "Solution count 4: 8.29147e+09 8.29398e+09 8.29827e+09 9.75713e+09 \n", "\n", @@ -666,6 +677,16 @@ "WARNING: Cannot get reduced costs for MIP.\n", "WARNING: Cannot get duals for MIP.\n" ] + }, + { + "data": { + "text/plain": [ + "{}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -696,7 +717,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "id": "67a6cd18", "metadata": { "ExecuteTime": { @@ -710,10 +731,10 @@ "output_type": "stream", "text": [ "Set parameter QCPDual to value 1\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n", "Model fingerprint: 0x2dfe4e1c\n", @@ -723,7 +744,7 @@ " Bounds range [1e+00, 1e+00]\n", " RHS range [3e+08, 3e+08]\n", "Presolve removed 1000 rows and 500 columns\n", - "Presolve time: 0.01s\n", + "Presolve time: 0.00s\n", "Presolved: 1 rows, 500 columns, 500 nonzeros\n", "\n", "Iteration Objective Primal Inf. Dual Inf. Time\n", @@ -733,13 +754,13 @@ "Solved in 1 iterations and 0.01 seconds (0.00 work units)\n", "Optimal objective 8.253596777e+09\n", "Set parameter QCPDual to value 1\n", - "Gurobi Optimizer version 10.0.1 build v10.0.1rc0 (linux64)\n", + "Gurobi Optimizer version 10.0.3 build v10.0.3rc0 (linux64)\n", "\n", - "CPU model: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 6 physical cores, 12 logical processors, using up to 12 threads\n", + "CPU model: 13th Gen Intel(R) Core(TM) i7-13800H, instruction set [SSE2|AVX|AVX2]\n", + "Thread count: 10 physical cores, 20 logical processors, using up to 20 threads\n", "\n", "Optimize a model with 1001 rows, 1000 columns and 2500 nonzeros\n", - "Model fingerprint: 0x20637200\n", + "Model fingerprint: 0x0f0924a1\n", "Variable types: 500 continuous, 500 integer (500 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 2e+06]\n", @@ -747,13 +768,16 @@ " Bounds range [1e+00, 1e+00]\n", " RHS range [3e+08, 3e+08]\n", "\n", - "User MIP start produced solution with objective 8.25814e+09 (0.01s)\n", + "User MIP start produced solution with objective 8.25814e+09 (0.00s)\n", "User MIP start produced solution with objective 8.25512e+09 (0.01s)\n", - "User MIP start produced solution with objective 8.25459e+09 (0.04s)\n", - "User MIP start produced solution with objective 8.25459e+09 (0.04s)\n", + "User MIP start produced solution with objective 8.25483e+09 (0.01s)\n", + "User MIP start produced solution with objective 8.25483e+09 (0.01s)\n", + "User MIP start produced solution with objective 8.25483e+09 (0.01s)\n", + "User MIP start produced solution with objective 8.25459e+09 (0.01s)\n", + "User MIP start produced solution with objective 8.25459e+09 (0.01s)\n", "Loaded user MIP start with objective 8.25459e+09\n", "\n", - "Presolve time: 0.01s\n", + "Presolve time: 0.00s\n", "Presolved: 1001 rows, 1000 columns, 2500 nonzeros\n", "Variable types: 500 continuous, 500 integer (500 binary)\n", "\n", @@ -777,10 +801,10 @@ " StrongCG: 1\n", " Flow cover: 1\n", "\n", - "Explored 1 nodes (575 simplex iterations) in 0.11 seconds (0.01 work units)\n", - "Thread count was 12 (of 12 available processors)\n", + "Explored 1 nodes (575 simplex iterations) in 0.09 seconds (0.01 work units)\n", + "Thread count was 20 (of 20 available processors)\n", "\n", - "Solution count 3: 8.25459e+09 8.25512e+09 8.25814e+09 \n", + "Solution count 4: 8.25459e+09 8.25483e+09 8.25512e+09 8.25814e+09 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 8.254590409970e+09, best bound 8.253768093811e+09, gap 0.0100%\n", @@ -788,7 +812,7 @@ "WARNING: Cannot get duals for MIP.\n", "obj = 8254590409.96973\n", " x = [1.0, 1.0, 0.0, 1.0, 1.0]\n", - " y = [935662.0949263407, 1604270.0218116897, 0.0, 1369560.835229226, 602828.5321028307]\n" + " y = [935662.0949262811, 1604270.0218116897, 0.0, 1369560.835229226, 602828.5321028307]\n" ] } ], @@ -826,7 +850,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/miplearn/problems/__init__.py b/miplearn/problems/__init__.py index 4932a43..bdf9921 100644 --- a/miplearn/problems/__init__.py +++ b/miplearn/problems/__init__.py @@ -1,3 +1,28 @@ # MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization # Copyright (C) 2020-2022, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. + +from typing import Any, Optional + +import gurobipy as gp +from pyomo import environ as pe + + +def _gurobipy_set_params(model: gp.Model, params: Optional[dict[str, Any]]) -> None: + assert isinstance(model, gp.Model) + if params is not None: + for (param_name, param_value) in params.items(): + setattr(model.params, param_name, param_value) + + +def _pyomo_set_params( + model: pe.ConcreteModel, + params: Optional[dict[str, Any]], + solver: str, +) -> None: + assert ( + solver == "gurobi_persistent" + ), "setting parameters is only supported with gurobi_persistent" + if solver == "gurobi_persistent" and params is not None: + for (param_name, param_value) in params.items(): + model.solver.set_gurobi_param(param_name, param_value) diff --git a/miplearn/problems/stab.py b/miplearn/problems/stab.py index f21a572..9a5e502 100644 --- a/miplearn/problems/stab.py +++ b/miplearn/problems/stab.py @@ -18,6 +18,8 @@ from networkx import Graph from scipy.stats import uniform, randint from scipy.stats.distributions import rv_frozen +from . import _gurobipy_set_params, _pyomo_set_params + logger = logging.getLogger(__name__) @@ -88,11 +90,10 @@ def build_stab_model_gurobipy( data: Union[str, MaxWeightStableSetData], params: Optional[dict[str, Any]] = None, ) -> GurobiModel: - data = _stab_read(data) model = gp.Model() - if params is not None: - for (param_name, param_value) in params.items(): - setattr(model.params, param_name, param_value) + _gurobipy_set_params(model, params) + + data = _stab_read(data) nodes = list(data.graph.nodes) # Variables and objective function @@ -152,18 +153,14 @@ def build_stab_model_pyomo( for clique in violations: m.add_constr(model.clique_eqs.add(sum(model.x[i] for i in clique) <= 1)) - m = PyomoModel( + pm = PyomoModel( model, solver, cuts_separate=cuts_separate, cuts_enforce=cuts_enforce, ) - - if solver == "gurobi_persistent" and params is not None: - for (param_name, param_value) in params.items(): - m.solver.set_gurobi_param(param_name, param_value) - - return m + _pyomo_set_params(pm, params, solver) + return pm def _stab_read(data: Union[str, MaxWeightStableSetData]) -> MaxWeightStableSetData: diff --git a/miplearn/problems/tsp.py b/miplearn/problems/tsp.py index dd910f4..209d24c 100644 --- a/miplearn/problems/tsp.py +++ b/miplearn/problems/tsp.py @@ -2,20 +2,23 @@ # Copyright (C) 2020-2022, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. +import logging from dataclasses import dataclass from typing import List, Tuple, Optional, Any, Union import gurobipy as gp import networkx as nx import numpy as np +import pyomo.environ as pe from gurobipy import quicksum, GRB, tuplelist +from miplearn.io import read_pkl_gz +from miplearn.problems import _gurobipy_set_params, _pyomo_set_params +from miplearn.solvers.gurobi import GurobiModel from scipy.spatial.distance import pdist, squareform from scipy.stats import uniform, randint from scipy.stats.distributions import rv_frozen -import logging -from miplearn.io import read_pkl_gz -from miplearn.solvers.gurobi import GurobiModel +from miplearn.solvers.pyomo import PyomoModel logger = logging.getLogger(__name__) @@ -112,15 +115,18 @@ class TravelingSalesmanGenerator: return n, cities -def build_tsp_model(data: Union[str, TravelingSalesmanData]) -> GurobiModel: - if isinstance(data, str): - data = read_pkl_gz(data) - assert isinstance(data, TravelingSalesmanData) +def build_tsp_model_gurobipy( + data: Union[str, TravelingSalesmanData], + params: Optional[dict[str, Any]] = None, +) -> GurobiModel: + + model = gp.Model() + _gurobipy_set_params(model, params) + data = _tsp_read(data) edges = tuplelist( (i, j) for i in range(data.n_cities) for j in range(i + 1, data.n_cities) ) - model = gp.Model() # Decision variables x = model.addVars(edges, vtype=GRB.BINARY, name="x") @@ -173,3 +179,75 @@ def build_tsp_model(data: Union[str, TravelingSalesmanData]) -> GurobiModel: lazy_separate=lazy_separate, lazy_enforce=lazy_enforce, ) + + +def build_tsp_model_pyomo( + data: Union[str, TravelingSalesmanData], + solver: str = "gurobi_persistent", + params: Optional[dict[str, Any]] = None, +) -> PyomoModel: + + model = pe.ConcreteModel() + data = _tsp_read(data) + + edges = tuplelist( + (i, j) for i in range(data.n_cities) for j in range(i + 1, data.n_cities) + ) + + # Decision variables + model.x = pe.Var(edges, domain=pe.Boolean, name="x") + model.obj = pe.Objective( + expr=sum(model.x[i, j] * data.distances[i, j] for (i, j) in edges) + ) + + # Eq: Must choose two edges adjacent to each node + model.degree_eqs = pe.ConstraintList() + for i in range(data.n_cities): + model.degree_eqs.add( + sum(model.x[min(i, j), max(i, j)] for j in range(data.n_cities) if i != j) + == 2 + ) + + # Eq: Subtour elimination + model.subtour_eqs = pe.ConstraintList() + + def lazy_separate(m: PyomoModel) -> List[Any]: + violations = [] + m.solver.cbGetSolution([model.x[e] for e in edges]) + x_val = {e: model.x[e].value for e in edges} + selected_edges = [e for e in edges if x_val[e] > 0.5] + graph = nx.Graph() + graph.add_edges_from(selected_edges) + for component in list(nx.connected_components(graph)): + if len(component) < data.n_cities: + cut_edges = tuple( + (e[0], e[1]) + for e in edges + if (e[0] in component and e[1] not in component) + or (e[0] not in component and e[1] in component) + ) + violations.append(cut_edges) + return violations + + def lazy_enforce(m: PyomoModel, violations: List[Any]) -> None: + logger.warning(f"Adding {len(violations)} subtour elimination constraints...") + for violation in violations: + m.add_constr( + model.subtour_eqs.add(sum(model.x[e[0], e[1]] for e in violation) >= 2) + ) + + pm = PyomoModel( + model, + solver, + lazy_separate=lazy_separate, + lazy_enforce=lazy_enforce, + ) + _pyomo_set_params(pm, params, solver) + return pm + + +def _tsp_read(data: Union[str, TravelingSalesmanData]) -> TravelingSalesmanData: + if isinstance(data, str): + data = read_pkl_gz(data) + assert isinstance(data, TravelingSalesmanData) + return data diff --git a/miplearn/solvers/pyomo.py b/miplearn/solvers/pyomo.py index f4c83a9..3493f67 100644 --- a/miplearn/solvers/pyomo.py +++ b/miplearn/solvers/pyomo.py @@ -53,7 +53,12 @@ class PyomoModel(AbstractModel): assert ( self.solver_name == "gurobi_persistent" ), "Callbacks are currently only supported on gurobi_persistent" - _gurobi_add_constr(self.solver, self.where, constr) + if self.where in [AbstractModel.WHERE_CUTS, AbstractModel.WHERE_LAZY]: + _gurobi_add_constr(self.solver, self.where, constr) + else: + # outside callbacks, add_constr shouldn't do anything, as the constraint + # has already been added to the ConstraintList object + pass def add_constrs( self, diff --git a/tests/components/lazy/test_mem.py b/tests/components/lazy/test_mem.py index 39ab650..88b2a74 100644 --- a/tests/components/lazy/test_mem.py +++ b/tests/components/lazy/test_mem.py @@ -10,53 +10,60 @@ from sklearn.neighbors import KNeighborsClassifier from miplearn.components.lazy.mem import MemorizingLazyComponent from miplearn.extractors.abstract import FeaturesExtractor -from miplearn.problems.tsp import build_tsp_model +from miplearn.problems.tsp import build_tsp_model_gurobipy, build_tsp_model_pyomo from miplearn.solvers.learning import LearningSolver def test_mem_component( - tsp_h5: List[str], + tsp_gp_h5: List[str], + tsp_pyo_h5: List[str], default_extractor: FeaturesExtractor, ) -> None: - clf = Mock(wraps=DummyClassifier()) - comp = MemorizingLazyComponent(clf=clf, extractor=default_extractor) - comp.fit(tsp_h5) - - # Should call fit method with correct arguments - clf.fit.assert_called() - x, y = clf.fit.call_args.args - assert x.shape == (3, 190) - assert y.tolist() == [ - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], - [1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0], - [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1], - ] - - # Should store violations - assert comp.constrs_ is not None - assert comp.n_features_ == 190 - assert comp.n_targets_ == 22 - assert len(comp.constrs_) == 22 - - # Call before-mip - stats: Dict[str, Any] = {} - model = Mock() - comp.before_mip(tsp_h5[0], model, stats) - - # Should call predict with correct args - clf.predict.assert_called() - (x_test,) = clf.predict.call_args.args - assert x_test.shape == (1, 190) + for h5 in [tsp_gp_h5, tsp_pyo_h5]: + clf = Mock(wraps=DummyClassifier()) + comp = MemorizingLazyComponent(clf=clf, extractor=default_extractor) + comp.fit(tsp_gp_h5) + + # Should call fit method with correct arguments + clf.fit.assert_called() + x, y = clf.fit.call_args.args + assert x.shape == (3, 190) + assert y.tolist() == [ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], + [1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0], + [1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1], + ] + + # Should store violations + assert comp.constrs_ is not None + assert comp.n_features_ == 190 + assert comp.n_targets_ == 20 + assert len(comp.constrs_) == 20 + + # Call before-mip + stats: Dict[str, Any] = {} + model = Mock() + comp.before_mip(tsp_gp_h5[0], model, stats) + + # Should call predict with correct args + clf.predict.assert_called() + (x_test,) = clf.predict.call_args.args + assert x_test.shape == (1, 190) def test_usage_tsp( - tsp_h5: List[str], + tsp_gp_h5: List[str], + tsp_pyo_h5: List[str], default_extractor: FeaturesExtractor, ) -> None: - # Should not crash - data_filenames = [f.replace(".h5", ".pkl.gz") for f in tsp_h5] - clf = KNeighborsClassifier(n_neighbors=1) - comp = MemorizingLazyComponent(clf=clf, extractor=default_extractor) - solver = LearningSolver(components=[comp]) - solver.fit(data_filenames) - solver.optimize(data_filenames[0], build_tsp_model) + for (h5, build_model) in [ + (tsp_pyo_h5, build_tsp_model_pyomo), + (tsp_gp_h5, build_tsp_model_gurobipy), + ]: + data_filenames = [f.replace(".h5", ".pkl.gz") for f in h5] + clf = KNeighborsClassifier(n_neighbors=1) + comp = MemorizingLazyComponent(clf=clf, extractor=default_extractor) + solver = LearningSolver(components=[comp]) + solver.fit(data_filenames) + stats = solver.optimize(data_filenames[0], build_model) # type: ignore + assert stats["Lazy Constraints: AOT"] > 0 diff --git a/tests/conftest.py b/tests/conftest.py index a664101..9d4847d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,8 +47,13 @@ def multiknapsack_h5(request: Any) -> List[str]: @pytest.fixture() -def tsp_h5(request: Any) -> List[str]: - return _h5_fixture("tsp*.h5", request) +def tsp_gp_h5(request: Any) -> List[str]: + return _h5_fixture("tsp-gp*.h5", request) + + +@pytest.fixture() +def tsp_pyo_h5(request: Any) -> List[str]: + return _h5_fixture("tsp-pyo*.h5", request) @pytest.fixture() diff --git a/tests/fixtures/gen_tsp.py b/tests/fixtures/gen_tsp.py index a180caa..09cbf94 100644 --- a/tests/fixtures/gen_tsp.py +++ b/tests/fixtures/gen_tsp.py @@ -5,7 +5,11 @@ from scipy.stats import uniform, randint from miplearn.collectors.basic import BasicCollector from miplearn.io import write_pkl_gz -from miplearn.problems.tsp import TravelingSalesmanGenerator, build_tsp_model +from miplearn.problems.tsp import ( + TravelingSalesmanGenerator, + build_tsp_model_gurobipy, + build_tsp_model_pyomo, +) np.random.seed(42) gen = TravelingSalesmanGenerator( @@ -16,7 +20,27 @@ gen = TravelingSalesmanGenerator( fix_cities=True, round=True, ) + data = gen.generate(3) -data_filenames = write_pkl_gz(data, dirname(__file__), prefix="tsp-n20-") + +params = {"seed": 42, "threads": 1} + +# Gurobipy +data_filenames = write_pkl_gz(data, dirname(__file__), prefix="tsp-gp-n20-") collector = BasicCollector() -collector.collect(data_filenames, build_tsp_model) +collector.collect( + data_filenames, + lambda d: build_tsp_model_gurobipy(d, params=params), + progress=True, + verbose=True, +) + +# Pyomo +data_filenames = write_pkl_gz(data, dirname(__file__), prefix="tsp-pyo-n20-") +collector = BasicCollector() +collector.collect( + data_filenames, + lambda d: build_tsp_model_pyomo(d, params=params), + progress=True, + verbose=True, +) diff --git a/tests/fixtures/tsp-gp-n20-00000.h5 b/tests/fixtures/tsp-gp-n20-00000.h5 new file mode 100644 index 0000000000000000000000000000000000000000..9313b703ddb5c1ac488711a636d4897c29d28029 GIT binary patch literal 32448 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/tsp-gp-n20-00000.mps.gz b/tests/fixtures/tsp-gp-n20-00000.mps.gz new file mode 100644 index 0000000000000000000000000000000000000000..a2d48c67acfdcf620b48d2580e3b345293178d61 GIT binary patch literal 6149 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/tsp-n20-00000.pkl.gz b/tests/fixtures/tsp-gp-n20-00000.pkl.gz similarity index 78% rename from tests/fixtures/tsp-n20-00000.pkl.gz rename to tests/fixtures/tsp-gp-n20-00000.pkl.gz index fcb3322106fb2b83c75eb8cb4eb77a0da08fab77..ef6c8357e96633041f1116674dcc2938c31cdf61 100644 GIT binary patch delta 24 fcmey#@rQ$7zMF$%aq-SnrvD|y1-j`Qd2(3*c2)@; delta 21 ccmeyv@soo`zMF$%`Yr2JrvD|y8~Jlt09ru@_5c6? diff --git a/tests/fixtures/tsp-gp-n20-00001.h5 b/tests/fixtures/tsp-gp-n20-00001.h5 new file mode 100644 index 0000000000000000000000000000000000000000..6ab818d93a14c6196f0bd7d6a50b8fe9a96d76ff GIT binary patch literal 29792 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 Mk&oN+rb>VR2k7Sc^8f$< literal 0 HcmV?d00001 diff --git a/tests/fixtures/tsp-gp-n20-00001.mps.gz b/tests/fixtures/tsp-gp-n20-00001.mps.gz new file mode 100644 index 0000000000000000000000000000000000000000..a12acd780e29b11953777ca90b75be9a74faac95 GIT binary patch literal 5286 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 diff --git a/tests/fixtures/tsp-gp-n20-00002.h5 b/tests/fixtures/tsp-gp-n20-00002.h5 new file mode 100644 index 0000000000000000000000000000000000000000..7cafa848458b1fcc1fd6107549934e5d671d3284 GIT binary patch literal 30720 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 XXN7JrE{z0lhkpC$_{U@w3(J22-vR)* literal 0 HcmV?d00001 diff --git a/tests/fixtures/tsp-gp-n20-00002.pkl.gz b/tests/fixtures/tsp-gp-n20-00002.pkl.gz new file mode 100644 index 0000000000000000000000000000000000000000..313606c7e94d6adeb4f8cc8aaeadf5d5523b4f05 GIT binary patch literal 1137 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 KcT=zR8UG87DWpvR diff --git a/tests/fixtures/tsp-n20-00001.mps.gz b/tests/fixtures/tsp-n20-00001.mps.gz deleted file mode 100644 index 7f8ad968d699967df65de09723f2394d18dd5a7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5371 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 diff --git a/tests/fixtures/tsp-n20-00002.pkl.gz b/tests/fixtures/tsp-n20-00002.pkl.gz deleted file mode 100644 index 09992bb67cad9c0ce6b7f0f8bc7a43a27282c86a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1134 zcmV-!1d;n6iwFpp+B;6lURTw-V`uva7!hSLKGdRs3UYfK?+Sibfi^Ra-kQ3APcc-{iTrI9XbB{huZq=imPUHVg?|ifF&QAUJcax=0+~(%yXaCN|&7Wc>a@DiD z@$HNq<0l(8cH@P&7k$llydC}E?O5MmJ=ga=Z||Ra`}U=`gJ<3jzx8&q@9m$LI`7=u zlFq$2(Yl|!&1#Ncj9B+hbpGot?N4;B{285T-GSEW|LLK(!IAveJFkzuUD3HE?JMbB zC9NB4UEHvE;TPkP{Lj6xxWqa?zNh`VXDA#}%|~7Nqx&y*g^T=;V{eyr{^V!fS66(F zZ5+W1zZmOJb$=jS6rZft=YG_8z3=!!ehI&8`CCwY>+)l$IL2k|yR>+1ytMoxj^Kq~ z4B}H3A11=3@K}Bem$CLu4{yR(fw)Y?XTK5mY-SK zp?I15&f8U|`*covWciCukdN>le$C@3yrv8MjK%w5q;;Cd(v?&AJFq&#d+rOLsrZ;G zKc-KtexVcOBfN)S#BrX#y9@jj9;wb9N*`kK`fHmvBl4L1fe(9Df6!I*3!NYz;XVA~ z-Qa~^jA2{vlz(OM@3q#);(ei`cPnmXoxh^E79yJu=qmb!PLPlA9)1zWIbLn+XGysK zuKVATXJ5(ByWajLo-YfhPT%509SRnBNgU7#@)6#{FX9MZ_{9Lf%$DWRisDt)xzohn zA3V18kvfDvp{wW@Izc|dd-#PP;5GM)xGO)dm0#9C}MI6B^x1jTx zs-OR(dnV5<|API6KIg#3k@`p-LhsR4@)(^UAK^XxB97pNUyQ$$hg14G#Ua z{RDNJ`bZr@@6lEC3!NYz;r*On2No~%idL_J#Cc^f~i>VqohdbqKvjSJAKK zg?xnf@QXNt7k)8P{EThCN?%C-MV~`ILEWZ4QisrcbQS)f6XYYj=R0u(uXMrB>5=WD z=~w9s>A&c6=qI?B`bgZ+dvq23LMO;acn`mbBY5H0Y`EDkk$yG*A9EQB_iGIR0E1d_ Am;e9( diff --git a/tests/fixtures/tsp-pyo-n20-00000.h5 b/tests/fixtures/tsp-pyo-n20-00000.h5 new file mode 100644 index 0000000000000000000000000000000000000000..02a0f98ac6aefbd952ce4eb22e3f36d8b94c04cd GIT binary patch literal 28630 zcmeHw2YeOf^7fu26hjdRB^1K~R|zfTB!Lk0(n6Oeoj+1iPC_8biRUCF6sdZVuHw}o zARsE(KtPlt0WZY@V!?~_8icEq07kh$$T!dP?ru&3SCRYqeaN5l(=*S`%sVsh%)IaJ zo~-NCj&0kQ@vY|TU9zNRe)5ga9;f028-f;f)=cZ(v29N;eTjRW!}H$^UoXw3Xu=CMfYitx70i%qn$0HuylggGmf^>w-X%4gpG1cV7Lz!=y-StWY@#V4 z+cY2HmaL&^K3-m4wycu2@uF#c%$&SxaqeI+>WiaYZ#5T~1JMD^kCjm@U7<~{?WcyuXfr{ zV?o-=YT89~fbDBdYwRuH{ZcjCd&K<5WsVz!;pyc&K649)zodJl<+kA;m(SB4yVW~) zd_G&gx6A9clV}aJvMF0P%z1Mix8(k=X|1%dQd(HPbT#)k-;&aDKDHN1so~~^u5Gr= z{b|9Qr6Nza@7Sr0b;89=_9UtK>=~h`c`i5UWp~>SN}|3q1~>V3M^cSerF|YNmOZ)S zuJFo{qXNf1I{#F!)*H4gFW&atFGs#?c_H`0g}g0W|1r_8O7bHa_7}G_uQE508esq z(5QZg|DJA=L;1fPZdqgY)Th1c1|7`ExxVWDyp^Y}epPc!Rsp!Q^3;{!nq!vVPcVM( zl%MvVzxr&z)hBCy6SL|3w()1uH(s8dx4cUJl#R<~=RMi@hnVq4rswWm_)B!&dsU9D z%s)5&V9Tq%sjp=WcDt&5u4x^11?Oc9jv4A-Y{}4Uo91;{v*B>qd-49;mSj8?rF9uQ zb4$kHM&CU8%&e%2C35BrT=!brIX`yp=~v2cZpeizhd-?6sJwh##hRU77|^j|)5hD| zhcsvymiyU^sr^^>Xn1%*!(pGdnRX>Ms@9h6|2TAhZhGaV?(6yPF}otVzYsb-@XJc& z>x_Eg%S+FskBZG}*L%Z-mY0@=T!}6}U}@%-YbUOGudS2wWW=NUu5L^J_&{C9yv#vA z)g4_Xf6Nm{PVVX#f8VwKhn{xzT{S-E?2?5?Klt=te_vho@YWXXtE8sa`Mlr7DWBFj zSZrnW3yqJxnG@fnY{joXnmDk*_~%+&+IwQ=ure3Vf7KwR`ri0`hs(v}zLT`N&(dS@ z(JOKmPD=mg`*-8I@5*g+Cj76bj_1w}_#o@+Pn>VK9@y~i`O!Z&$$ISC6M1iZoKQ1z zYYoT#1?vtp+PfjTd!7BMm+Bw+tocI&w_lHWrRSHGQuaqSUN^mZ&f%!*CodOUJ8$iG z+3VMb9=$Yro;~1X?%*TkKI{_OeoAJapB_j%Tet6}Lvi`ZZ9nk8USdxDLygY%KIH$G z1zQhZn2=xjYL^yI%sZnmZ@J>g(EI`G9dCv1NvYFh_rm5At5@1xyJn}C_SRc_f1*1s zx$hh6vYuV~T8Zx(op1E;*>R1pj%cxR&hW}dQun_5&5-)z-@MRo#g5BME{_?N*P-+E z$ehTGX0zXz_eOq=f&Y3mx9g5s$6F5gX-l@bJ~IZtyTw*vLOD$f?pez>H-FR5Q_m*X zYF7U6QJ?cUwuLEwEA{xXQpqV(oR=q-c;r~IO*tj<2b?ThJ!9~L-|l|izg4+bU(ZT9 z@WFz_!~-9kjg2|*K~ny}bML|MVyPXWy6kOsC8l^)ja=Jh$eu-wOXe zX{}*@KveAPcfOcEBiDDuh2qW&WzW}f9v+yx-M4o7{z0iz=5MqnUo)rt!knr@YUj_| z{@jN@+{8oH@#fEv{P0+}4b#s(yE;AY=luaF&5w1*9ZSz1^1Y!k zy|#19f{|4YdFH6*wVBua@^)OCeC^?nMmhgk{Zi9|3F%)yFxo#NbWDwfZ3mJp^e6pd; zX1#Q)k7qpf#rPLL?L4{Ng7-GHt$H{ueC)V$vp4-%dBgUW+iJh~-i%7`M3?z7)92y3 znbGAR?Z4f(@5;aBx~nC6Zz~^H7uJWS3|&;axVEK7G1)9vui56Kg#`F)!}aX(YHAJP z>Ij+>yvO+XRTOTRj~j(4Q??BM&duSQJVY10;5E0+yB zi-IJy9=K_i&(F(Sk)}S<5_H1TsVy@Gzy7SvcXM#bt=INteZTvYv^8rNogG?Y;-KM; zcJ4eFQ2Xmr*Xpl-{^0(0>H5e^GhgcU^4zCGHht)Rq+iP6$IiZcu;iduKk^GaeRh22 z#*IHFR9-NyMEt%jea0>NDzEk8t6R&w5b?NionT!|88^>kjSEVXddsbHIk{@WZ8C0> z{=-dUfVzS98f){>Y~j>ZU-OL%FKxaD^A3a&Ri9t_--q=_WOItgCG+k-Nj5jn**5n7 zK{kKi4Sb;O{h|H%C?KQDMN?;!Z%yNxs3_w3k1DvVae$1=-HEMZUclgG= zkhyFvD!+5x(#N>7qORHyblIvl!M}WL+SSJG%Cza-v8`4NcVKAB*uYiBuM0Ia(@TS2 zUVp!RFVWJDaGbhb1N3NVN_=E1%#hs$w(&WIO86tQTx&nCRyrgxY2TCvlWzSp!$ zBCf8zT=Ke?_KghGwJW_>Ro6O*^%~lm$<=FXF#;dZh9B+t7wrKtSwUOT^Rvbpe$T9~ zebnVbOKmQ77y57NqCG0y+M0i*`@3sTiLokL-_j=rYimToUpsStUy^oGU&h3#G61TCK&A8$NTu zPHlp4t7_9iChpXBa9F)nvEORG5~QD&y?f&k?MHG$Pp`|?Dv54&t_%nd4ir?%Dn+-A=#bkN{dN_eYebPp!}gAWNhoc{qb+eAYU~BJ;s@qoGLbh^^{?w+zBxf zag11VH`GId^+abpIVmn@jKl4Ax`dtPPDqZ|(^4=oQAt&K@bxukrAMha_4EV@Q#4%8 zbQxxk2o}SMX-Ua$Jt{d?mnijQXR>6$>Gti4iRp1-*x^cXB_N>SjF9lquy99gv~Q%- z5f_&bli)~B)!nI4sR{1Xgcx^oUtRASmFh~!&|Oh7P{(JW-O(U8SiFG@5{>TB!yS@? zkq%R7V3>&PN+T2+Z6pCw$6vA0vtut0F}#E*FC>F}dq}#RiQ+=a>X68Tf`i=_Gq(9n zbI`DG0(?73%gM~7R`Mwb8b`X`!#5F>`8lmx6@Z53XfU7|*h zigaQHL`gUOFJGnhKX8$T#)U;UY1-7`+c(OU5EY#$i(piW!!4^oR(o=)Y+ueaWA#fR z9J0RkKx_rk3CU5eQT6WlwcR>47TaBj?oNwNb*8y=N22WA$!e*}49yM;3JS88{C}|z z+-4SyD8p_gN<+8Ft?2spbUIUYmm@JMgEMaKu|~s!f+Mg?$TkuxCyQnti@qVZj9*#t zMhs1FDF$pw-Q)z}1{jO4$TXJ=Cj)RTrpK;nZ0QiG0eU~#ZhZB287YZ+pJWG!7?N#P zHd9B6oTlVVDjTXWJ0-`YB}K!yeA+nNscm8fbcjlUEVVH`a>G?SrrJP*^)ShW+9ffL z+(tm^)2N7C*Oidpvia7BnrX@yV-Rw~LLKYG;0snx-k=$h1^hb@6&iVpOuM zLkSamAr_Ko$hC}|E7EwbUvn(L8K$;q-<;^k6H(wY_{sf}^E<=hkJbm?6? z^$5DDYOGu$dZ-u+!ybMsD??qi%6Bmf-2?YBa4!S*GVs451J-XL@&n;b9}m5~-^j9j zY`>U4B}g8RhZxDcV}B!CH9POYKQ0%YJ$mW5`Fq(Nn~O;+yZozYF3jJLO{yL%``wR+ zR`juT^vFd+^I6e5HW$O*{OO@TE*GAU5U1Ypxw!Jg>rN{d9>1NI*MI%-!U@LA?xx2J zUmEeT*X`uunY%C-gC7_-^mcOb>|L0PU)r}k=&`}w@lc*{eZG6gKCLTm-}L@F%eqXu zzP4P@Pg91MZ#O=y&HVIJ?OT^?U9Msyp97t~TD53sfKTT&U;p3;ygVbg_Woj(Ds}1p zVWYpSJ{vmd{lJc+d(K;SWMk+c|6LL8tV;{rS?j(T`S#(ibEkco+wt$cuf#gC1|$Uj zebbcj4d;w%{n5D2`{#Xrrue3aJvn^_=F}Yc;Muv?vQnHsXYctue~oKtv!!nishvD@ z!_l#y9ZJ9U-N6UE9`4_3Rh@79ei-{!c>G%~XNyS@U5+%Zo)N!t_nXhB4gGGjdtvDI zvL!+u9JKiGn6;<823HBmJvSx3LKnZEL)NZ2x_8H*61zXy`Q#Jd*U1iwbET{slD+HW zyb=fdmJJ;-aX`G+ikkW-S;?f3&1;Zqwi`XPzI?wDlKbQrCBV z{GsLFe&v;4Kl8-S;xBEm9dRE%+PPC)wfs_lZMk*O#l|bfY_0yt&hr;X4;-2oe`a>? zf6mOVGWVEmW3ybp9Ov})4Lg+C)IO`~mA~}5c>eT-ir1Q*Y83IlYr)>|?5gertFK_zn(6$%77jE*oUN`s9)>BDefA{XBQx9F}lu}|; z{_3q8G7c;lT76ikGv8Km#rx4WJY@CI%2w=Elgh!Tb>+gXjp|@r%i&Kz=A&c4_>+%$ zZ2lQYY2W+4mw|g3xR-%@8Mxy!U|qNUU%&s4_LzI-9e;20SiO@GMRQ@^>zefT?|D7% z^Y(=qiMeC%dB5#+dWDsX-*KOJ(wN!Zbf5RvU|YG{$;H{bFc}>GNI-%{mL=j3Z++}Dl!05Lq!ITrNap1fec0@)I3%Z2}4B$ zz=!}C5db3yGX@LgV4-R-0H*`M!2lc#z`;!#>I_aO!xzfXgsSiXObr!2IF=4QHZ?p8 zRh|Lp8GxPv=ox^X!;L{g)gS;40^lG34g%mH`TIUS3RQyuI0%4)05}MMgCYbly#SmG z0H+JUxd70z9WAKZ1BmM6QJ;tdh|~!5h8h8+MgXZ1Kxzb#8euhu-Rce^HNxr-^3(`a zTloM|K7iB+Amsx{jX;Uj2p}~ANR0qeBY-06q5bczzI&@r54^Yf%$0$4`2T11aU+xM zhufD>u3n*ByF$5gg)%y!YA*!fUI<{CP0T?O(jRTm*RH)#X=2WQQ(4cHyj0Bq) zJ#}uXu1k-*zCiX9L~1XPX7Sp*w!1DQMZV@=S$9$xL{3fC|QF zLCnIsVxn7c3O5Tlj6^0%6`|%aGlv8)G61>-FdIzG85}B0j%Y+EjR-aQzt%{c9`Z|N zk8a^~4?0i8=_044;0%2rbYP?_D$Nf4<9LQAO@ zvNoC`S4=d^^`QC&{i2%8>KtDGC_OZS(gRX@Kq>-MSm^;NJs_nAr1Vfkro|4#yl|x{ zo>e8TG{v)$Z$~DT6{M+P^>My3N$O*=N~z#pLm4s!613Ukj988tuTVxSlm>({KB1}< z0Kc+aOk-;HP&UooxRMx=l@&<^8J-b+H5*n|@vQhgc20Ou(!y0_9H<_r0l-)RR67~v zL2pt8fGPk*D_Bsm!ex;t=?Kx%3IJ6AXho_(6;y*N08{~>ip+~Dm>*RDr~*J0kBV`& za?HGr9WxCp{va3?i0A#RF2_taS67t9VGO@&TlL04X0p$_H|*^njEekkSLXaZh05OPGv9C_M_L z9ig-$ls1IYlTei|02T|t+-B5b0>?C}<_3;w)`+fP3sO6=5r;Ianz<2&9IM$FamcZn zu~=l;(BX^&KyB{=Oq)iMg=1PZ(j*+(wH7G=Eu##aG5|SN!mWH;sa2Vk+f#HQM-^&E za&Fu=)asT|)&>Hg=7m&oSdlZ5;$f0X&kDTAeYn!IV)xi_tmt`GQF>PV9=nh=1CO(c znuB>CZkF5Zpx>_Tjag@d$Rt=7p==MKW>pxU9CNlpRkZTPA61v$zMXE0opoUPh)mQt)YXMF97CCf>f3a4FpXM0BT6c)DTJ=m;yBbs38-ehEQr? zLev1DhD?kaLaBj?QUib*5;Qf0QUjBq1^_jrSe&0wb_0sXGaCTA0e~q5U`7FG7JzmE zOf#w*q~v4>rCqcD?E=s)0POH+qo5wcC~~$d+G(f^C%|hcH5NQI7O0?u$diU)cF;J?4gj+wL8?)P27)ep#UYJC z4WYDw#-RoPZHPl^2&D!BpauXnq*16Llp1IpY5-6}8ig7{se#6!1^_jr_S6tc4fFyv z0H`5Vq=ryxpsLgWpvH|=1&8ScV0Qp8xd6;901X4s@{PR!4($NY4gl={&<+6Y0MO1& zI|#K=6$hd%>Zyu@rz#FabCfE3H|sv|lszD24@lVqQuct9J)nXPV&=pdJ6$OITqrFG zr5&NPB9u0SG9sa>1^}idwS2%~TmYsq6%{z9ITaH)YKjFXO|=}Q4QU1K$PlIL$Uygz zAx%c}LK&-28W+k~h0?T86)ON^1>kf6XcWLSreX!hG^b((hX&ERi~uc4fKDVpD-oa< z2t0ZL`BywlO6@B^jDULsLm*OBgp@(tu!opevu5tiY8B+yOsn!(@Ux@xX5>ynQud9U z$+0<3?M?vsb>{jRkROA9w-~w0!nE)BqL;&bP%18;y$q)c2kvvmUr~;Ho;weD^ zI0k?csRS89=~t-9SMxxrI1d1(1b{UFU{U}$4*)s>pgk0r4ghGm&^AI&p$&l?)}F(P ztYyVRN|m0Ko+4)mrDtWP$Q6hhYbAr%9g2sz)mR|KL*$%`UF~;f2QeA44yiAc+E|xL zx!_h;8T4onDoE(eqEIGLD03*3DHLkzsY(b#RRVy{0ZbdJ65yCtR3*S+000b8vOwPzsPa3M5Gs2&Di?qd;=s6Vwqb<;7y$qy0AK_FG!I~!QxSl}2+(EpX!QveCOKAJ zSw*r~MTM;7{nvlr;sjAW&IiCOpXwY_f;yV@qXZ#v41k$@N|0bm04R|RQ-VxW0ziqR zf)b>J5&%jhH4p^y{-Py|2`DoGIl zMF13`C=>xu1V9n0LlFQ)02CFem#R`hmjkJ+0;#M5sjLF2+7u)VNLjWr<#F9+rO2al zNQv?Rq zf=yY4q=cfv`gT)T7Q&HHE@jYtytyeT4?&?I&OL0(%0OmCapjR4_qQdbu*I@V~K#9{6s^0G+tHP|-IwCy36S4eg%Z;r2Q z*!GaeMx{p0H~wE#HqEcF0yDF!vN_xg%^2<)=FT!+9)80}wsbHKspomV-|zGP^?JXq*VTRQ>t3JhzVGY0Us&VLoh$HqYX>>=_Hq|+_kSqhCMql- zO!z10>h1-4ntgYO>#axj{s~{eRZpJ_F+}KzXQSOU@0oeU@W!q8dM;2-(r`<2yx(i{ zqdFRGu>h6!Un`3V8gS*LDbmLD5Uq~ogdD7B$({8kgd85Got^HTo!}nIAy21gPJbTe z1fQ~o9RFI**M4FkUX?0CzNV zy2RRgbQ-lK3$zk~KOLGmIn4<^z#Se9dt&JI_0{BnQ}yAoK>HoDGE7rI#GPPanN z$c{$3`px(3XP&QpV-ED1e17yzdT(*+`G&{6w&fxV?_t3JXL^hJxYh|3hmHId@PlMc8ebcC#dv*LIS1WHLiAgBH6fZYlQ<@F7UYtg2WmgX9 z3`mb}-bTVY;qEtA4dMe*c2rXIf>Qd%*>v~q9HUQ-77z1RNm`v3*V#76W2$T{*XC`T zS$Al$*HM$kQBQo9i^Onx2r2c@{K6Mi*a4#>#9Fz3&2(1)3`a4$cj&xLV1g}E8KlLE z$VZBAtRJUeKOwNmHj$Qrx*1JrF}T@Z_VzRdb$Q#inR}L$fe9l3ZsxR3BU<-V_{s%t zzG6K_v>u@HRs3(&V*Ed<_@%fHn@owi#|+2wdA<&6HS3wm^uXlb!)h}0(EP-#RGM<%m) zEKCBw@3wZ;ra^=eMsYp5=JC`JXc_&=$r=S$9uPZ0h~%r#h5hbv3p~y zbCfmVm(D#gyEBvync#&B=dQtUNPj%DG;;S5W0!;(jOu#ySA|V~?cHAE2NAD6bX>B{ zQ-E#V&!u-0$(<*oK-o5aK(z{O%@ysX8xGuR52MK6VB2^4fc5`?H7EVdRS}U%r$kYl zXGZSmtfm9+)`8F3f;1t>QIz}x`Ml})rKwT9g58I^?nxXQl!JcgKuzbo(YjAfkI84r z9Gx;;L$S{CPU38dZX%c*$V6sDKp^SMjXLRM^%Y7HUvwayWBqWQdDCNkiWP)n0MW7d ztZ=Ml*iRWyBsnyCwnHs9%dN|6@3160Ubg>Mdk+Q2?;gM-bzx19iGITB>eh)-AZK~7 zI9n*t!xCh|)Vo{>QzyX$BQxy$5;x4R`WaFYo|&ph(Vb41!aW5D9Y|pq#o;X9H+Adh zaOx!klld0a*LOb~m;`$pCEHD^bI0(|=x}jx=4*6fk>Kf(R)iyu&#=6;k8Paqo8_-= z-9B<;<2o$9b2)scG26mN+BE%id1p@r&nt1!%F)~eeGdF*$wcz) z7cTn+f}h^sNwv%1@15W%cZ}BPW`0l-zs^1}?JqG`Jp40hLv}QuzgJeV|0lb{0b0wc zyGZOudKB{x7K*@MrI2|a_2Rpd+*)4FF7}x0bDR7R-2uEFyrd$_W&ilXOXmhYol=@X zPQ^F(sYo(zE2a8o2yTd4OxWGlF+B4%-QXS0=joMgBKJundo8Fm?Ni(wtl4ZC<0E5j zQI*%kQXLP!BXbW>Kk7VMZiMGT=U zR_Lrcgr=P0vhYN)yHcRksO4yEX1%~Vf|#X@q$YUJH*r-vQBvJf1m|QL&hgxVk(A@P zx;ktRF0hjv+XKob$6^Ru27|k~Qmn9j9oSm>6p0%ekHrcdRf#ENKya}LDRIyhg#kVc zs+Ar)s+dxgKI$S$iYU(HnM`AFQXNE7A_729pm(5zEgC=xF_xdXghOUfyjV3|OVYv0 zrznrNcB(4HM(R^IA-;%nDkLyl%j=P)@y_bP$Wln@c;!RI*c1@d3_hW$Eu?kMi{m^J zC8TvOX0dwWM8|vuzvJ!i3NnCS>Z>JhXm|iC zan8}vF&gZUI^N(U@D+9JC~rQ@r9&?QKkWamhWDV#{k;_LmY4!T~C>#>bx9jTt2Wr6!i z8Q+7*%IKWW5pQ!tRkmSo1Tw~L=`V%@wh7Kw4s)I!gZli)70I3 zC-z-$VfS?U7*uK}B~5jQ1#c!aYZPv$R|YpQVK+0IWC;NZNk+S&JF_-}7MA2@Qpu50`etEHpKY3@}<2vBReaJzhly&wzU@UY9P>hblCRt<24~QNJ z?v2a~tz}QGZP#;tqC0R)0h;nwXp&g~B}T!BNJP|B=Z3umV}E&99z&pEdh*_Mbo$j| z5}pf4QBt~MbN|7#jEl&O9u^t|BnYUVdz;o;3Ip;ZKhTpuj)jt+^91Yo3jZMEPUu4t zPGU8nalB|!zR%5P}DBIQ=556@%UugcTlA=;mBsumnF~e;2vj&>*gm8w=MoX>W(`XKKh^!5FY~NAU z7em|+pVFl<1_6u_IF{k$EL@I^CB$L!j`c!27kq;(7P5o(@4B@J$~9@UHXn<|_C7uq zOxmgh&|2AIpGe7P;#E!5IC#}_bnE)ekHfX?f@Vcf+RSB-j77hUPQUEV z8DPSh;eGFAaYx?E_tIAOk&bbZcfQLB&K>zCYKgD&o@{QbgVT&i8;=n&y_{{m7hBf= zZW05YNIk2uTD|g>o;rvS_B6RUURlveMsFqh&!jL9?>f9uA*mF);f{P8x<*yd2&4!W z6SKK>-ys)3L*U4RB(`8G6!<)Ut`+QH-B#Ygw?**A&srU{Zi;GS>l9{p0bVILG}lD; z-X|~Ov0@zA9nc*p!<<&w0dS8$fi4??Wp6TK{gr#4l{|DPV0+2?)g3t!wpK=G9wPS9 z$1ZxUhKksZmGYCUpfFgtGl%FGRF+u|{>=SQ+DCabdpc%3}WQ!m&%%sGIO(6IGM;Oq%6Uc=!WoO=Rce#VjjXpG1#(G%L)<*HWkYZke_|IV=xsgoh_W~d?NcHUQA+Gi=TAsG{pjp0y z#lImo#FSRG@l-o*ZIzh#B?WytHa&vz(!08*(OwFkqT6bUdjW(KI+19M^|A+YENl(X zxJW47Y#K43hpB^1rZ>l4Qc$WhF;A_!hi07g%vbL8@EGT+bDD=d>GU~yyfz0P`#?b% z%OohACsH`mdo2qEliwItA)!ydK(_kqg)$8*uo-5gnVm-uA9EtRZZInUCYuqnD=V0q2T?p08nYD{%h%!%FtP!Ht-o#=+icSF5%^9r0dUFjH0!mjSNeXkT9z-;Ap>N|}+~ z6oVeWdZy>QkAb1=zZVo;@JTBhId_fXD&h-gA*dTmJ$*Un#s2)KBfqx~rwemz4hLO> zn?4EsrmA{pb z;Ug-wS-}+=_peeIauisWp2milfw}_{B#3dyxjh_zbm7xDk7`Lxhk-*xSwHC(I|%|J zz#hf)r!v+M>`IFrJ=T=kuHHLkzCCScVgsDvEDfvDE+hV5WjGa-s8Ea`=!9ux)$+*X zGp8qCfTPGa#i?9s$k^wV2bCHY3k70?-k~My17`A^$KSoK2W_u-{lv+51r|v66OQ7E zGO2naALpu7zQr=l4T}Sasj1}(ZFMe$PXDKR)oh9-n2h%ktz1e&H{m-cIvH54TKb5( zQki@d!W@9lYZSE$3YUmzOeHk4Yusc~nKC#nU9}lDuqv?Jy|faj>f9|Fa91N6PwfL6 zD>iah!NXpG$(N7#AF;gsXxcRnG^zX4VCGqCRAHnsm|-lP1~deMA4v1X-!6UKob!}p zlXt{;w|>*02vMdfz6o5zcDP7G72Lv4+Cl`Ms<@>!0tlysCc5=5@?8c_e@KdRxs+NX z&>H`I+U$x3wI7=)Bf8KVB5cXc(UO|Y^svYX0IjR5+j#q@Mw!;7~B5;XLhS{I@Q17F_y8Z+xS>X~v3|m#2a@#H! z#k8-sW4qu=tW4zDG~{R+a(5SDd39loM;oZ?1X4%@1z_GML(@Fj_h=KvkMlmt^$g3S zT8-#mgh|||XM0XR>u8SRH%DD|1=qO$`qYsz63`HfDHAhaKFU{`p}&N;HD9*1L|twyim0s;KVR+e;rJf^dsL@hFzBnoZ6D@SbDLj)?7(%O<@4chfiQZLrQcn z8M-&_VgJzwTiSa@YK>TWj;K~@NnA-gwg|4oO#}etJc4A&kh@oSq8_9`v+j|*M~cXY zi4xcr!I*SEvKnyNiu1ak7UHtD<#sJOZ_9xaDd)9(IVEo=>knYhba1>ryiq_3<2neA zw}*G9{Y?a1B9pNy$f?2iI;m!9kkj&HnbM5qB|OSu8Tum6>kR;Sy9P}82;SHth1nPZ zQ^vw2co1x9%m6%E0nvw^frN;3=^^zCVN7TF6)v3<|$Km>Zxbf{}JQ`T?{r;$IeNiuUNIxe0d*j2Z93cB4Bdr15hXxL4;%rR!f_y{4k)a zSV{5;1TxJ$_TB?=;o$9PdFbsmLE+pA#R(1tAi?UGeKz_)=Xa1i@<3|5AxaE*4-*hu zp4}zBsF)3E#`OKV$vZjp`@ER|gcR9Ha(FD;G{QUW9Lh-Cqm^V!etiO zFDC$#Kosli3^8WO6gsJnQA7JLLK%n|uw^?xcgIqkw+cOG&iw=~xopLmmx^sF;mTQ$ z$Lkf_q8yF-`HCxP@iIUHJBDUwuS7~dl)~p>)A|T*kV2^a2f$_z+E|LL&toqMBoE6k z5KZD1_qR-I_LpPYBDO4@vnV4FsOqQ+ZIuQ^;z}TQhzU6^4B>M=;OS#{xH>W9}Dviz1=Fb1_72=p8F>bZaB# zBJcq5jExD?cZ%EXjYFnoGXUqv4!y+^p)NHR+W|GXf+VD38c!%6VZl^Vv!VQNTQPmI zti++3L!raocCs(m?x+%HpeaSEF4?l+`E0PaUOde&0EXuC(VB_`aG3j;jEF6g14?gI zsA6Hiw0E*Uc1coDm^-H{&-UoZbn5l_Qm@0oibR>h-*_MHv3wD;byQW4UZc$3SD1gv zdV|kl9@+RZSi0^vxGsgr(FCC&_H;^Ns5Omu7Ezb@M-qF=?|N1Bn+^rmp8mq!|M#>&3dnW?-Jk!UKXXb5wqCe zfNqSN6JnYJNd`IM`6cV))n5%O4h7N8y3>({zd4=l#FkD1L^NZppmzm?Do9ne!6dwo zoD6<`Ni3a&!4KARD;5^~g3dZv2w^{9d3yTcj}1!=_9`eF0tgBSmhB%g+vwkQr64!- zPba6$T|jN1syweVefler1+Xyihsiso$AQm8!v!4^a*pqr#MI-z=;n zKT7;`$Wsu6mD_OtS^ymKyu0I}8$l!p>v z;47itiSklvY}uSVIqGcwoM>qpRTh#@FSafAaIKbNZ^Ay>oJ?0NK9jFd%{AmKHc{8E zP5w(4SJ!@!ytRY5&SF;!=K&DjW%Y^Uueu}vouy#ryq z5=x$wdvG;WKj|UkA~IUy4M#6wDwIih^~GT$(IP`Hnqo>MO$xn7VqD6SD z$qQM$VWJRTqG50`dXY3nSK%`SL>*-6?-8W%>7#ARJ8i@e+2u#yQS_RRzK7FmzVPIJ z=iWi~w-B|G_^yJ?wn;Bfq*n(8-g6s{Bhpee}`2P6jbyxcYb_Fkr?`hdCU+Q(W|=ct`9l zV|LnjK57y>_7=WQkJ|;!cjCMJmLFu7Cl;%(Ji14ubCb=PSZIbadIGF-lf5+gZl!!S z3azCEiOIjqY!x#>qVtm63uYSW$DB3+%tzTkw~y>{v=m4uW!N*+2{aTl(CJi06rRaM zq#S0U%4w9Zj$9&DX7;1$ODV3}u}SFRjVT?@r@uul4%WHQS4jNtBepBTF;hlxB%$HdzaENfr$Jhtx6184|TgewMtH++_`hZa@ zx03JDDQ8JCpfTXR3QxWW1*~D6(UAMfU=jD4HBzy2JZd0b@Uz9NCq2q>+p=og6at@o zZ@fENIEjC|e&c-v3IV!{P+)i3+@28Gf0&$EGflA-?B8Z1Xx(LwiGPc_J^gN%=q(_xI!5uyS>FS>r8guqYmETzw9sFL` z-QRZXz^|LlEs*nFNRmabF6OP~!yH33{kQ);R{9ysP}K+Y(?&#V;nvyM{0pYuiexPn zf>oDxmTL{ZC~p2exc^LVY#SKO*sUqc74dT|@Qh5{pFxgMS^l{lRcA-FQH-+#}H zW1f%NX(RtA)>`dcYB-&mwEd&o#QzKT(QR%sC2mF|g7PnZmxmY) zCH!{j|FZkPjs8^MV13YUs7CfS#Jl=`sR{9}5%*5?@F6Jwf>6FYb}GUmYr>_oa5~uj zH$A6=J)>B)k0S)-Z@!OeJD9IMD6oF1HG0%?55^QhdYzQk_lLG4Qw8{0+pxy181uVP9Phn}aD-w=}L{1y*?)1Pb8 zVq#zRqs087VfD9i>lTytmCvM_z<=DPZ4z<4B*hn~NiY8@$+f=Fr9n*{^G5ERBjdDD z&v)QxF7gDMw?i7kVD7NENm5F{2K3EFyA9Y-qlPaiJM>!WDt@fnbFIyg0d^N4<1#D+xnFue8y*n)u$D+>-I2*8+^vMw?x$N3q;DMX8xJsMQV#foYHqY2tqVxwcz=$ z&yd}d*Re&1!S$D{LlM^Z82`cc9wPxshJUfOFng5QQDjh~C`b(DpNITS82|ilTEqDN zhq;!60IQ>uI(5VD4@M{zy?;Qf=n;6S`7eUJ?y6`S_K?0ECaG5b4>igp1gg*dFOF3= zqQ?ug7LQbvpjGIGoGQ>O$dhLwj;7-43KyO;`OArk-m^k|B1d#(n@gH3Fm#hNZ#WR6 zNKh-FYoL$3;PVNiuZL>`E??M#MIZdQB_F3XKoV&DygZTCv%~FZ9Q;@0Z_}BVLaS!{ z><5WfM=WQ#s;E(+txCve+P&y5+t!>XH`Dp0x=H+mO}u}zE^kJ7#E z6y|t;3sww5CxN6T_x(b4-W$!PotcE4z;p!9Ox?~_--J}W33)o%)-c(&H`x|H*|xZ0 zb!HyyzO!`XIF4*wm$md|6>d&QpN{I=w6Wh;s68r9;c_lR1@A0*o;kFgd9|E*O`W}+ zK6^WJ=09~NsbZ}ERe9vxC-lyP^$2b7O5Zp6-IM_Zl$#m5n>(peRK zKzqK8W6ntV*t~N<<=9-Nkt6t2Jh<)TL@wye$jVZ??WAiUiBr?TpC&&BfrZuI-0?C-E<5UgK`2 z26uhgGR9xhXtP=I<0lllZrVR@9+yqc>6>=ZxH0+VbAxxSz`xX^sj}18Y>Sq=FO85Z z237$hY5!8^ZAALA88o;*eu;VQeV_kds^>3B`W&zSPmKBWHr3VmxZ17FeHtA}sp#c1 z8#8JUtah=uH*OzOZ}P^4PVjk3ul-7mNwW)`(DRYrrl8k8YSwF+m{FC`iZX~<8mys` z%*BG?E55RR*Y3gES(28)$ qc6QBv;F|5?n%(S@{jPO2EC=|zs;c0f{kB8g85qfb-Ip~D4ElfnKbl1V literal 0 HcmV?d00001 diff --git a/tests/fixtures/tsp-pyo-n20-00000.pkl.gz b/tests/fixtures/tsp-pyo-n20-00000.pkl.gz new file mode 100644 index 0000000000000000000000000000000000000000..d927584018518beab4f18ea4bb232556e53204e1 GIT binary patch literal 1149 zcmY+Ai8tE^0LMMG(lJ@PcDxnYYOlIPl3waQri-ITXgv=lY`WGQ{Ye(L9MeamY>uvP z&M;S31do~! zp$$W)nEFQZFVD8;bv;6>3e3%c5lL&!poxifq4w47n{{ zF>;A*-fVw^h?JCYuB-dJ8qJ{3eiMC!H-#8qux4gf5BB3gOO^(h7TT>$ZUfwzH`9qM zVKzqL+Y*L5zXC1XV|*Eh5M-z#tAL8KvfFiS{vi#vuIFCBCXmNBfukChgpuC3O!yQ8 z%h2SQsi`SILpp<|A;RNU>6JD?uE`G-!$lox9^bY@K_28UM#cJt2Sc|XtMZvWZd}jKL!7zFYYkG0t9$DObw9s*jAM%S3K2&dyBif%M8TN&adj&pAATb=J?>6` z_?wQbh_U#})v)>&aA9 zY0%|G8x;vbrs*2Cl?l5xfJL8Sv&9dZGb#Ze43DP00Gnl{&#v(($?agWaA`0(NBYXT z2fVp1Z(9TBmPTgK%4W-K{w_gsBigUPAA-Umi`ZSe1xaHQGV`;5+f7au+Qz zPkGTkZ^t<7YnGSG_|uHhrWi^RlX8q+FX}u+qcXS8sfHVaAy!4eOI~4F{)QFxu(#$7 z^g(f@WUh6GdiCk>L&Kpf zQ~4um-ft&n77$cCSo>WYq4q8xz&l3a(MF%>w!Gz zinyFZ3s`th+O=KilqnFi*(<9CDkJHF8S&?cUpd-(wH=R*eR($p9<6O4j4I~XS1Glo zRk;#`Ity2@e~~INCnv?t%=PnU$<)Eb?R{2@OWv~s=mGndxwP*r*pv^n zW)xGcL1Nh+ivtx@HAYDG10v6R1Ngd#I!x_u^;lKq_do!={^G{5|9O>?bmeYIuaH{A sKP)zGuW?gQHi1dFOCCvuckg{Mu?snoHvMWNFgoMMm%d7ieM?X8cUa?l8UO$Q literal 0 HcmV?d00001 diff --git a/tests/fixtures/tsp-pyo-n20-00001.h5 b/tests/fixtures/tsp-pyo-n20-00001.h5 new file mode 100644 index 0000000000000000000000000000000000000000..35e07dde18a664280f1d5623ae5003d436a748ba GIT binary patch literal 25974 zcmeHQ30zdw_kY8pHY$do;1WEs5>0^t6w!X7B7(V~xM!jR3@|#(&jk27^Ptx52;~#+}g0)! zx3ib}4U*_HAdPQ~_8^lKfSkXbpB+TGp&3C_oZSRtQ$W ztius|34*&NfGsthyPDbj>>@`Cgf!BkDj&J!BR&+(!t&Jc*~PQKY_-}@<>NW=eD|`a z4x-#qjwUSK7QnTndF)5)yC8HFylV>Hi)5y;i;9s zbPej(S>ABblTqQ!pHXT?`CiUx#y6cduom{27~1;8k(kCEYgzTNE|?UQ<5S;%v}a z8T>f%k<+@si*v?2hGY*bedK?W60X>s-rqj4`sK5)>^3`DnmpHVwddZieeS$|reLvr zQa?9;@SCYoB`ud_XD0F!-JK!#7i66`R3gqh0*mJ)6*!rb^Fv1uJUu^wli$j3o%U-y zd)j2nW^ShvuiW2Ur!3>by>Fe9_Evyv85iz)I45naLm=hnDc66#d@s%E-dN}F!?P}D zrxYdZxjnmVqeJ+UC)^B)F6EI;fbrf zJ1owgTGV6SnX$uCQ_4;EFDyyQ*>S7umU?||cACE`xz^=8@ABccC8~g!p|)Fp3+T7B zH1xhMqkED6?P@8je;&W2=cXxN^xe=tpz;3Cmu__kF7f-i_RSFATYs2-XcbZZ^2rI# zXItmLQPL*r{QY}>Tq;>VW}f>W33;FVdZhXKV`0I+42w1uq-R{SC|=cS;PTuN$FI%n z@LSQgqJ|yZz7SFd`q&IyVw&g|YRI1TrCaNvT9RFP&ttcy)y~fya_x}&ebt_v%x!Cju8IsA7g#)hQBroq zzt-;_xa)N0^-FWk>Ax|=4oGgDv&-+xfp^N*-MRULy7kzilh+;2mVH`p%S-L8Z`*11 zr?|Y{Y@C0cLfc_|M>b3OXm`N9eJPXce0wVCPEP6X&G)>!Tm88vH!AmVX7IHcr62A# z#Mi!;kXK%ilse9I{p#h6K&``_W`A}y)M|Gj_?ye+b+(7@&f#-2G4!(oin`-p76gxw zCdwP-SvRNssc+KO_Vih+%U2Z3W8bg&PF_uY>=eW83Dw`sv(CCwy?pR_n?{MDubnvl zfn&#)JAOAS=HwSkb-I&Z{238`@{5@AA;q69TYv4>AC^p=>f$;*%ym{>*J&ZHGd*2X zjjpqmYp1@oc1DY}4ezX9*>1#&*GH^ub8&^+#g!czu4vM5rE1ZN=8IO2j_7r0#DoRW zlYfk!^hNZPn}y?N6~32KIB{d)`^AN}gBSZY__W!Wl|FY@xZhprS8cdhZKW_tW#zA` zb)acdM9l9I8*Ck?^>Li3a!h^KarR5;Q@f?lXr4Y@J7>=qfj>s}I<#n3gY;>G(`Wjo zr;bjaZ9Qmez@Qng&GE|o>E_{IkA6L!+PguOY-sncf zTXq%N_$Ea~e*Wj2tqbR7=+D^{=Q|ajQ#j@KDVX_iyVJRsmgP5CcFsDq>*@7AzsGEs z^bb}?%-(Qp$&4Gy&r7NqN^CASHJl!DFS88!uWDmzs^sODGujyCjk7VmPpL zl*4c4F>)@QS!!2yq;yhg``x1rKQy}5=9D(!yI00I`g$eBFgx9^8g2w%cfVHW>g6lH z@9A$idNTHt-vSa2|I%I-aZcGXBhYN^vY(yb=glIOFl}z5d5NL@zuHxMpIEEo{u%Eb zOIdLEtw{k(H)VBca5~;6d0g@AtV{KG9rDX|S+HqFy$xZtFQr?xZ<$#R$ zZSgndU3ZL*x==r(@C0{gKa-L_u5px5E11h>p7tPn)pvz?=#D6+u&kJMQDJtC~$su9a3z z(Oo=gIdz+G$Fuj1qzR=qV$wo8LFrOE6??C61A}_wZ899<#*Rc6`TG$aVDL)}U6ZyS zgJYRFcwqSTiy>>%&c9c^*;1i;sp!%*`O9A&W_{v!X8X=}J~RWR)WHZWZ6f$bm1i|F=JLi*rI<;xEcCy zk7p6hDR_-59l8IIXhtnkm2;b)o<1>}fA0pqQjI#8nKc0Jhv}GS#;ihcz~lipyM_ey z7OI0$76M!o@Pl8~Z)MX{zQdR9h4{3UDLm);ONVr4MY7U@1q)Xejt_@IxaS^FyK}#w zE`l}QfniZbxNer7E;JVSC~w?av!!cakP^;NKX$u@(5$4X#R?Y2L|9!=Hy#&oA%~+W zBQovv_4qZN`}OE5SQpm&T@dO)b0c9?qJxct9g(SP&F{_8r9k?!nW4$`T!h4|1K^Rc$T&S=W z1RRB;%fG}3=K9gcD#LXq0kaEI0;^> z)oX-fq@h-6-%KHrxIQxn9~H&}w}CL-bHY*K2z6_|IpTz%1S9Q)g5!J62$zWKRk))- zs0Xr*gr;Xx&kHfYbrI~g4k{LEg0d!pm&fp{!cKsWLgSwY+!huCbQX5!#oQL&0@zq6 zGuW329-zTNIC9W^BlQ1=9}lhyQrQWd^2fb6sUNdj@yEUPHi|&AXvFY!V3irO%EC+E z1-<^nSp2;eQjSaIN1R)DT&nCNPWO>}+@2T<>1zO(6;G@Sg#yyBU%G=9668cAf1W#(bFwj4^2l*>G#(amz zhLUG7T0J3HA}i4$L)kf(^Hs9*zM42OR&7+rXyPG&){d9Qq<~AamM)YIMi2J_PMry;g8hxB-ic`mFO>x?AQ#++7_Eg6iwTYrp4IM>1hNv`eczD2QNQQz$aM%b9 z#9)+$3k~stCKZ$LLW#~00LbHi&=C^U*Q^;w@noYA8LI3J;WFr82uQ02EO+zpFv-k_ zc8`of%gO;zc7u`w&mdQDR2YqCqS)K0je)#$(?`1DDU}sJ5ygHmD)>Q%2Y!0Hd3$@{ z0akj#xtE&{KUKjg+J^`Ffa1V0N&P)h9OT=!ZX-!}VRPjU51g|8%DxzZ2&g&{lxVp( zEZ5irYB1b@fp(>{1cPy;7_ZmHnVPf80sjngt}09*Grb;|S`cu5W4tNad~Q{qojufJ zM*ACZ2B@K${-4iM)ia!=p57`SAMY?<Jrzbg*{o*;EA&d4;*Txye)hzgP#J zGK!Lwt^bOZe@9jsVlc#sMvYFLNFKK{o1@-tka@O@c)@CBKIvQ1lJOsAJS{i9V9__> zdXtPF{_#d5F7U{enPw}Yv`k|Y>mUwIjfvH1 z63rG;*xU6_3aj~+E+(i=pwiV4iTjUwlv=B!=~=Or!$k!w+VQmM&{cq_YDZ3q(q;}z zGVE(iAQE>;(KXJXgMCULt`Sw>x~7!}T$*4+A5j;$fw0xPr5J`HVWfhWjd^nvq=M}# zuPK%NO&Vh>xD3&Ssl!K#2*3}IHZ5sZVG!ag3Glr|Ij~bmuWr2p+ll^ay&kGI3a)t| ztnuLx+HivjHi1ZkQS8~Rx7#B{Bj8%iOhlR`s=qYx+2y1B8EY-W7vMz%UPRzU1YSho z8AL#S76K0lANhLd(>){GYo#dQj}pM@d{4>rD*KFV@>hDlXBUgN%rbYHKQF7Yu{fxi zx97oF@aM;zve(M~_UobcR*DX0v2f+@idNZJ^s~Rc@Y%(}{1xKJDj$paZ=T&C$HMH{ zsjc{*UoSLDo;^>m7k*#Yw#L)MV$5?Gi-oqPZBG-6iO*py>aRQYKlpCnY>9lv5Z;>n zAULRAANxaDhXeX8=rT1e{o{Er9~=_x+ToAA3!AvUv;EzeJ_2n6pcfQ3E-c-5qx5Un7M-%LYiz8i_u8JEJ~6e> z@1=W*1>ep-`)9-eW!6uHio4U-?U;DWC;yKPNAo)7z1F+axEb#>OPIXTy=ZdExf_bm zoS>hsWMt-M9)I^@qs__>#|C`zLD~8e-|2e;U%9vLrP4LqY?E(9jM;7&we|Jf;_128 z#*Ue{7wk`1J2oKea7ysate(lU-Zp-hQQ(s~NuQP#T1T4SIB&aT+jYRZ!S;# zW$2lexAq72o;$yI;*b-&voAE+F~;w7vkr|4Tc2LD;9gVl)p3T)S6W5nmKWL#znwGg z=T}N^m(R$`YLf4JMcCq-wP4P^J;(bGzkG7qt+V>H$Q(^%nwQW12E`%E%08=p&a^U{ zN{GMG1Siq2z!h0%R#5gHh`@^oJTDQDuiO6T z_y5uh0u-p}RsP<__SDT255|Ju>vH<^_q^u!c^9gDEUb%%SKRadjr+W7&*Ala;Qi}6 z<+}JAu{h+-R*2{6o_Akr)+67YdeZ%W&T|-x3pR^rC2;q?~&Db+tB4{ zV)6TP7>f@M)H8p1zRF)F@b~|w!aX4#{GmPGBjPc{)fHa_Yu*yxIf6G7BEl@InSz1eBjMi-5r_LO>QFAd3)?1>RC;pwt;C>x_WhK|q}m zP-g_xxphmCi~~yM10~adGV>8|31&WWWEmO}zU=3ejSLJnG6EVI0ga4+M)r|90cD*K zkhut`69VdlfI796Iss*!5Kt!s)CmD~LO`8-0Z@1mkS_?xT?FI{0`ftHCCJJH5s4Fz z;+157bwkK zpky6TwiY6wwGaUjpSv;_DG#!xnk>wG(McOwL=;o&RKLo)5F^* zSHZ@|rH?C<)*?PWHjy3(H`$6|r$8W}5n)lI7F^t@1p&3d;zeqKQjCCdJ~;>Hq#G!+ z5rMoXl8wkA?FhIsW+QUA8fGKzePjUw8mZC_N1RF%h&Yw@CgR|BLwe+$i1rg=ne8?h z8^%LKJ!wy1!NvHH<-}v*MIKx05s~o^>>^AHy2g;~i3(j|JamEeMO3jnkS$O-Bqdi6 z*v5>kC=k{gOdv~v^30Nza85P?rBQ(L3Se0R2Fnrx(v5)YVooE6Yhx}Whb%)t3ZYJj zf%U=@Nenr&;5{)g3M2tRAO;457!UzYNhd~x${oRzu2)coj6^^_BcK5gkV32u8bQ7o zF}upZyi+4(ks;X|mF$K}wx6=dsqKLHB87@SaniP3i%B zvH+t)IYU4eARr46kOc_H0tBQU0awQ?Kn^Ly>Yx$jykdxngIOd%S!76#C1sKE&@F~! zHzx0+K~QvIs~PfBVHyhzMvz1T-Q7(u{c~83ZJQfMgJm43+@NARrk8 zB!hrts5+<=36YJ2$Vju=!90?g#3L_N6&b8Zp`1giF&dQ+4NHiPD`!b1r-=}2#Vk;C zXl-HRB9D!Wac8ZFSSupdiUDO>*8g^p1G5HPPFab>|m#3i8kNC!~T zfyGD?2uKGwMG`0gE^Q7G?lkC$j@(SeyWGwag9-D=9`m9?D^3QG=U$ENYTLKr&dw z#6dtDEL!3qAPyEOaS#ya(RD{Ml`Du?R6*2_s(|s(6ULJbe_&l=a-40 z{btuZvIi>J1wxOnpeN_{;iyx#!3oL>P)Z9>syd+L2T<}BD9bhi$~FSZHUi2v0&7 z;E98PI2d-~ARrEgpEwAJgB3&^1jNC@Ar1oKP~8xV5aVIpFdibt!|*U3BF01084nTT z$;O-QXR`TJOH2;!Cby-lEPP!2xC)|x!K%R5oCnqmFp?yI%JY^pt8`2TIuo%Ce7uvX6kWkASj|fGc6~LJn8MvJb%`5uh{OerjML@AdK#CEN=7(zmIiv>x=|Mnx5Re`Oqz3`%d1M7) zZdk?RhFUCe&A8Qw^qnt!t&{5+2?b{PB1?_nk#{9UuDnj8C$S|QYeCivxxVe~sQb=^7J z7T5{!$Isz9L-@#H_{$uqMyAk|-;OufVnZb(ire$H+Sn#r zare!K`1Hry)ug|FQV7*i9{y~*F8h&7`0Bj|a&+()?oJ(fXJUoN$C+Am;wf2zgrVddC|iU44Do{o>y+7-_FRD-eXV*4raY{h6Igq7^T5< z65n)(qq0PNY^*F%4_^0T67|eFx#z z#YI9I^t+3%zTbELALn=GaOXaA=kCnh=gz$yNkEYAT5pa0%gxo9$Jxt?$MG(V2L}AX zc^#ZxuSJ_Ys$#8oXdFU$Dkv3VOM^okrbDRFnXU*S#?Ox7JHJ|OEYb7tvb0mN6 zu5dUtU;GS{yy(km_POZ0E#Y@@@g*$B=VZgy%>VExMe^t7?dD%Ol71&go4% zC&Fq`zkX)iX#ROryX5P6zJW%cFI{#6wTp{>zkcprno0hgD~w!{{CS+?$13S(mex00 zYVT^2;o#tCUxga*b%?1ph@m@wl=+;ZXkLz6=ymU-(&rSV^Kww3mEK2<>!`h-XxnrD zPrttKHJ=|8p1MYFx0yPAdufFddhxNMmhR)y^m(-S&T0e639n^6k-?5-^2gkDUb}iC zqn*{{k1gxG`*W{(Ypr~#z3L+yePS-0nl8JSd@ctH&7y~X{XACa{&;b6fa=arqI-6l z^7+w=fXZtHN<`p}C3WIrmI9w}zEaW}Umx0BBB`@@EV)o5DH#@7&L%uafZ$Y&iR>}+ zqh{Fq1-!ZJFT_PeEic%)GL=9I_JJ_u7MM)<<~Jlb%G_4+mp;=ZDREjIYBM#q<}%eb zfalsA9Oc{(rAJMekP>SU7f&1b>}?FG^JIaH>1$Gr8f;Q0VSb z7$Xh+!_yYLw9ns)LZowvWG4wUyU+78b^%rfRtEjV4Qvq`y^8qSg95{6jci9+5p zdM`qMigdcyfX{Q24jB|~U-b@z6pxqdMr@x@h3vke*tvy+*k!*Y z2dB4?%bpk$g`gPEl>(}K#w9@M{o`EWJuIqDyjWW1N#pJkuN@J z1^TM})ykm!m&pC(QQvZO8Xkjk;DUTBz*hz!avq}b)qD=0;4Ea{ad)$Qc_^&)%M)i& z2 zT`^7<0rAR#7{2p6^)lTR`+Y`i4lFyW-i8ANQ-_%bu?z8+kdG>gAwrsjQDmu%C-({Z zzYWQ{t5=N>uw&U7_clPonQ&!AU07CC6Wfz^PZ(hbp9Tf(9P-JAV`hsgcOF_Qwt_ps z-LF*Zluw``LX`L9&4ZS|Gs3EdZaivhlLr=6Q6Y5uCKrMd$OBJlMhQJ`V9RmsGOP}GP_L7yTPRQ3C@T7?6PIY z(!ana+Dr{aGHx27TY0@zktG}XWaTjpMM38|0GS^=p<711RZcT68KN0g$7?9(oiXw> zIOX!yv!i96ItT;_YvijtN2&pklbPW6_0Mab&i9$mxi5n3#whDL-?#8MD&sUoT1ZmL zBA-AICj`ZtHc=xrwvZ1`LG${$%UzXhGE8|ga^`9`h+DG>UOXU}guIP5f01_-hkZDO zN_j1f|NdR2U5+*qh9p3y;`hU15zVxSVnLNd7W7^|F=i!LXfKo2Vtdr_kWn#14&KN{;4{>e_0BYuSi{iW^loLK`DNW@yjnKqU6Wl0V5%)`!#&^9Wo1Dt zT5Ad+nDe^2%Qcm3c`_lgI4fK)NpCq>?`C61>TKy=l=W6xYhu=t`9CVBoMu(`=?}=H+n^uv z9}6IB#Xy*GouE)pqZvpZjV7TPQj(>A369NwW1#+h>Gl(x()=Tp){niQMV%Ui8?l9t z2DTPq*bQuce7{@GmcSw|&9~9`b6y*>;4wKgu?0*@v{C`)<#p#*>C^P6jbHsgSdx9hP3lfd_KK1#&;i{As z!C4?H*&x~;2`Yz3%Sm;p-0L@;1c$u}auU$;73BXx1LPZNep$Vs25Dg7G(}Ppw;q%E zTLwvpasJQ}97JBKvCI zQ+3WcQ#!R9$P&xa%LX~85GoO>0F&4!b7D*rW9#2r4{D5&hY_mhE9?o%F zm$dg zN{Ne@2nG|&BAdx+;t@jxv(dR(!!9A<=Yzb{Euxgq%_&rJqO;*+VGX$74mfv)OPLT% zZRQ)o=UyOs;Wj&vT5!ULdC!WwSAD1 z-edu+_ZX%?rtlF)zdJ$Z3^z{>DW^>w%F%{YRD|ECKv!8uQfMI0Vt4E2_HZp(@v z9;Wz?y1&jgYIwqd((~-q3Ee*>n?1@>^|9JnOak2Q#~8* zZw!r^p1rfzE4|_~t<%|X6wQYp$V7j$jUj@L1ttwvgr(X@i|g$GX{+kmmszUqD>*N8 zD#}awL0~@PfIB zA=eVmP3g|Cd+(XP;D@{d?REt}q@Q#PPHPs}3{*vpMoE3T_H~vjXYvDrwJnn%v1mih z;@o<{!lY+1nQsm>N0qasU2$YmQT_?R+JKB_c(;_KUgHGgM{h2^{DOJ)CZZ#5q?YK< zlDEwOJut=$aTc+R6x5t=(Ys&l_{Vi^v{iw%d@an9!2+3Q!|E1iW% zaCGg8z!6irbd!E0_RWZDvyY}Ou-T)0(hp|A_?mSt9X;Oeeb8ES+r1!+&X~thy-%>znr^a^gzpD%&AIz~<-{sS#WKqE zgXc9#|IF{Z8_~}0LLAz$E6IL~d>0M&`wjiFTl#o}z*C_OwPHPjU^8N2y)wy9$5aiu zGOJ~GFB%h~`EDiYAA}zBzh^2!rjcwN@`~>PM~Q|RD)KG&;|)ppe%`wi4`I4eVK+tK zGWJ`U&c~No7q(Dz<@$Eb*S9N&H3)YlmYa2u*BrB2YKeDo>|pAwoDd0~uHC1=fYw5i z*`kFvI+0eUQToevA&pN{8fWC`7#WH)5Nft?AJIq)_b~$ZG31&-lyG(@@E^c5#&%B- z*vKm`3iyacuB;gl19#JSGF3iq7cy?w!Nn(^xo_EF_?Tr9_z{;7rpwtr<$t{C-UCY> zJ+g*fO-3(80fu8z~PsAj=stOYHjtH~aI0GOaeh3yzHEx2ZP_wFHL zQSe6Xx$;~G{b50wVZqf42+JS}ZaGj()>e1L^FGz+i%)4EcyQF!sL_<8coPF`dD0=^ zS3L;_pV%hw<|HI?ach$3CxCZ=Ij8j8F6f?Wf-90ePxk9%LL0)uc0y zYmgH_I)B4|Ay>`;yinlJmMbu05X`)b-Ax7$n6nO>hQZ>&cMv{830Jp{g-`dNIMr25W)H#Cbu)H_POe}mROWrOE|{B~oz7GX=$Dv~SjneVM^q>^1|C|c zw|Y7M#)}^F?7H7T^ z5raLUjP|Jh%a5)7A0)Zg$ps-;Xhko3eYBx0xrH=z+Q|~-O%T7y|5JMzG{hS|NVih3vyZ~AbVn!wnzbW@<<}(SiZtCyuiTL?Wm3KR08dplw>i625@5XVXEt#G z?5j$oy0372-Blf5ximO#%Dg|Lr{nOsug2Y}`(NK7t08 zNnh7bp~=yR9*=h$JH=f(@i!~!#DBR|KUh#;VZJqCC9SP_MM_#*YOj)aL7oLfl_V7y znbOj!4F2tb0{9POqA`?Kk)?qq{b_^41lh@MOiP#tPMHbz6tDF76?$y)4kkwFajCyt z+*x8JAR_`&57d{}T8sQ*34YS`if8@$Z9zXs$7z|lY7*2oyNV?` zLE6a@aC1#zI9H};<$a(`r5scN*|LZ7C$yzewx%hq^cOhWXl9=0bt{d8D+QK}bKt2& zsH%Xpix$lxfnTDAqGY`^-E1^lo*$BS2%D1PCHlNfE|oA?DUf=wO>v$50XTsf*-C5! z3#RQi$Ky|BYFW<{>nnVT{_L%P{3aK?o&Z?(TOqMYr)W!51IYX!c9IGu! zh4IP*$n&nuKWc0hUK?r+po!hTahtazWQ%JtFvud_MQkV{S@qOgf2A~lDhvF_^3Ey= zJ53I4*nYv2{(=^uTDpa&x7TlC*P4lNIgdZA}M%C31va;#SZz%qnKs5-`U!cru;F5E%>f#K9 zV^aXLKi1}%$nn669hWx{q_se~8jv=`C|T<=`#3ZKTURKw$PuIgwBhFJ5aIV=w3bS8 z$%x=t*wIk-e40Q-idD)6(om+%OV+o#+9T2C&=!X7Dbl3uACptlhZeJeTzH3UhYge) zksa0s%B5xVQ{@1@&lS_j?-)!uTdMM>H`HKjwb7mhFbo@$4?kaVBTd|*=ZwpTCj2ob zqK@gK1_c;;_N)3F)u+4%yQD+8gqz?m!7LMk*C?P-5q%NI6bLnn>%4@BE_S^ zAgdo{o+_uq99|+vt`p8$LQeD*8Yj1K;se7>9hQdJ3xr|>TJG3uH#rIO6sY8J$Nde* zJxo%}bM%p*0k3IfQwoQd^aq{wvT6wB${p8Vv|TL-b`2op-zFVFXAgn161RyJX~AnX zFh8O-PYe2?1m@=0pO{hmFZQ&)o_hV&-Q3=~fq6#=_D52i`oRHz{o^l4GBszG>_&Mp zuV~MG$ie4(8(vTQ4@Ofr-@u2IKW3-EjaI-dn{UX5L|C$2<;6Cmyas6x-0p6CdE7tN zlUkVYNi3`Paj5LI994NSohZ)|nmuj!MzLjoRZFUEAbbehDjWBOLs`64WgAn#5H^cn zd-N%Z&_>Ess@dYv{@c_P>)vf@_Vd-z%j0a%cl#t=1X1d_pASzXl4YOWZ4X$fjq=Rd z18P+`U{R7|^PjLpu+lf?Nfd3=Gu6B^N}X|*Q=VNmS+KyOTimcIa5mV>(L?kHHS70> zg|Yd*bBU&_ZCnbad+NmrS8TM0^MUq8^XZ7!hJI|X^b3W0`HwPilC*LynA!&R?&B(X z0at4Y$GzEMpBUm9J4(V%pmGcAg3a9YjT=5hVOg|v*3=J?Y^$MmkRgmR6!nWu0*4tc znTKHeTI@{uA1_cZ7yc?}kzndY7h0beJ4(KvQ*$_fI_LLwPVM|?Y})T^>W=-vUN+Cn z7@K#~W3|+8v4;(ns2%<7RCY0n0ID2Fs|GA;j@^3b zZImr)DF3%Oqfct)=%7z3EE0RL&c&^!;T6%VrJbF(PYG0=dEf1q{y{0H_WvnZb68I4 zdo~p%6!t~phvIx-@b@tHp3|hyILglvY>|}qakY2IQyy$o zDw^D^?_>5dc+4l_8<$D0I7rCKfg4|i-Saf?bEDLAOU6?J=&1$t^eObz1$ycQJq?7O zlChuCvY#@spK`IE!r4#j8BbdoPkR_oM;K2Fb@!d#f4*(h(`*v&-`KQec)RIrgIe;u SFvs`AA9?G}g{lnJwf_U4a3mK1 literal 0 HcmV?d00001 diff --git a/tests/fixtures/tsp-pyo-n20-00001.pkl.gz b/tests/fixtures/tsp-pyo-n20-00001.pkl.gz new file mode 100644 index 0000000000000000000000000000000000000000..293e30ceca3370a51c871997f21e81a5f4f321f2 GIT binary patch literal 1138 zcmYk2eK6Yx9LCLcWlL8atxcO`MaOhR-xN)lb8VP6f+*9hJ-}CNwh@y3u$^CWc7k{bdA6B>5$gRqfV(|Nh+beLfGo(A>O=*b!%HOe3bc zre>tL5>C0hy1%@WZmEe$CK08mq{%OA@|N5D{<41?IsPpg5ZK2K-tkMY{;=NEKjA#t z+;ZfUHH4XvQ;q*S@aDo$vvptGDz>|-?*Q~nrs&>C%tZi-%5)xXpe;IVX!b{iIl_m| ztEY&Z7R1g!S+fyMSsBBHU^biqI8RS6ndMd&e^mTODZRi@@jwcJ8<*6%u`d`Tz380N zLLX5v_-LVZBf7nGJE!;Y-7Ss>C2)ly(PDT<51Ne-syd-&!0*4Xw)LLdq+psM`e<7F zQ%8UyN#ljm)>iyV=pPU!&>@!wD9JqBxO>W=#n-K^!(ZNXh2Cr8C=%~46KwRx(fA@l zS(QJk&d9qps06JAn$kG%&e~^wn&DE@jyc|Hxv3{!KL%Ht8v(Jx9sLh|{MH zZslj!#2GhcpF?G2!t63cCZa&8bxn)j-iv9%c?kYEi!tMmJbMVCHhw(YU8VEV}NPQaqhaq7fq3?WI6MrNw{MQjA5qAqpSN>YW`GLL$3 zJ-DU_!w=A3vY*GufnLSF$m#(>8?TBQ53YOZ`0UUhRb=se*>2^}%Ksb)*7lO6DkfLM z0`Sl?7<8HPHcllAjBi9l7<-)R(P>4zXAW7}h3Rh74{h1L8$5M)U%G_#lkXM>EI04Z z;A1xtSvi?=T}y?pR>))D&#n~e@>WvAxvcNmp{$QX%s^DS0T#vot)8@EB`F0bNLrT$ zPbMQki^?Tw^Tjp?!D3DQ2}JnH_Axyy>Iru9otNC6jSDq$abxau8TozRCJ~@Xgou^- zIw)vK^JQynef&@lDas2lSz~Wat22sJT~bz=2-QpWHaG9}gRCP6dcb z9Wc7`!Rs`cwaDSWGz?g`vQZ+Co$Pm9*YnGtBK@SbzN3r{O4cFcOu5Lx`_orXF=L%u zzv8og#G|L5zgL#8dgI-E6(8Eyd^PP0IaZeS!s|vw?q6fU} s;4yBoJJ$vO_o9BT;-K1LK@umzE~AKBFyp%dsoLve9y$>U#lE)RR910 literal 0 HcmV?d00001 diff --git a/tests/fixtures/tsp-pyo-n20-00002.h5 b/tests/fixtures/tsp-pyo-n20-00002.h5 new file mode 100644 index 0000000000000000000000000000000000000000..06cd8803a76a60563ad07c3841ad72bbb1fec1d7 GIT binary patch literal 26902 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 I+AIG44;{`8umAu6 literal 0 HcmV?d00001 diff --git a/tests/fixtures/tsp-pyo-n20-00002.mps.gz b/tests/fixtures/tsp-pyo-n20-00002.mps.gz new file mode 100644 index 0000000000000000000000000000000000000000..1de5170905e8b32a772274044e2f9bbeffb041d9 GIT binary patch literal 8673 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/tsp-pyo-n20-00002.pkl.gz b/tests/fixtures/tsp-pyo-n20-00002.pkl.gz new file mode 100644 index 0000000000000000000000000000000000000000..d24be4700adcb4ac10d31bad24c86daf2d469abf GIT binary patch literal 1138 zcmV-&1daP2iwFp#bGc;#|8#S3EpT~nEp9R}Eif=JFfuN1Yis~rmp^P6lURTw-V`uva7!hSLKGdRs3UYfK?+Sibfi^Ra-kQ3APcc-{iTrI9XbB{huZq=imPUHVg?|ifF&QAUJcax=0+~(%yXaCN|&7Wc> za@DiD@$HNq<0l(8cH@P&7k$llydC}E?O5MmJ=ga=Z||Ra`}U=`gJ<3jzx8&q@9m$L zI`7=ulFq$2(Yl|!&1#Ncj9B+hbpGot?N4;B{285T-GSEW|LLK(!IAveJFkzuUD3HE z?JMbBC9NB4UEHvE;TPkP{Lj6xxWqa?zNh`VXDA#}%|~7Nqx&y*g^T=;V{eyr{^V!f zS66(FZ5+W1zZmOJb$=jS6rZft=YG_8z3=!!ehI&8`CCwY>+)l$IL2k|yR>+1ytMox zj^Kq~4B}H3A11=3@K}Bem$CLu4{yR(fw)Y?XTK5 zmY-SKp?I15&f8U|`*covWciCukdN>le$C@3yrv8MjK%w5q;;Cd(v?&AJFq&#d+rOL zsrZ;GKc-KtexVcOBfN)S#BrX#y9@jj9;wb9N*`kK`fHmvBl4L1fe(9Df6!I*3!NYz z;XVA~-Qa~^jA2{vlz(OM@3q#);(ei`cPnmXoxh^E79yJu=qmb!PLPlA9)1zWIbLn+ zXGysKuKVATXJ5(ByWajLo-YfhPT%509SRnBNgU7#@)6#{FX9MZ_{9Lf%$DWRisDt) zxzohnA3V18kvfDvp{wW@Izc|dd-#PP;5GM)xGO)dm0#9C}MI6B^ zx1jTxs-OR(dnV5<|API6KIg#3k@`p-LhsR4@)(^UAK^XxB97pNUyQ$$hg14G#Ua{RDNJ`bZr@@6lEC3!NYz;r*On2No~%idL_J#Cc^f~i>VqohdbqKvj zSJAKKg?xnf@QXNt7k)8P{EThCN?%C-MV~`ILEWZ4QisrcbQS)f6XYYj=R0u(uXMrB z>5=WD=~w9s>A&c6=qI?B`bgZ+dvq23LMO;acn`mbBY5H0Y`EDkk$yG*A9EQB_iGIR E0Ns*vKmY&$ literal 0 HcmV?d00001 diff --git a/tests/problems/test_tsp.py b/tests/problems/test_tsp.py index 46b3a3b..584c55e 100644 --- a/tests/problems/test_tsp.py +++ b/tests/problems/test_tsp.py @@ -6,7 +6,7 @@ import numpy as np from miplearn.problems.tsp import ( TravelingSalesmanData, TravelingSalesmanGenerator, - build_tsp_model, + build_tsp_model_gurobipy, ) from scipy.spatial.distance import pdist, squareform from scipy.stats import randint, uniform @@ -51,7 +51,7 @@ def test_tsp() -> None: ) ), ) - model = build_tsp_model(data) + model = build_tsp_model_gurobipy(data) model.optimize() assert model.inner.getAttr("x", model.inner.getVars()) == [ 1.0,