diff --git a/docs/guide/problems.ipynb b/docs/guide/problems.ipynb index fda56a7..17afc43 100644 --- a/docs/guide/problems.ipynb +++ b/docs/guide/problems.ipynb @@ -129,14 +129,15 @@ "8 [ 8.47 21.9 16.58 15.37 3.76 3.91 1.57 20.57 14.76 18.61] 94.58\n", "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 2026-11-23\n", - "Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (linux64 - \"Ubuntu 24.04.3 LTS\")\n", + "Set parameter LicenseID to value 389975\n", + "Gurobi Optimizer version 13.0.0 build v13.0.0rc1 (linux64 - \"Ubuntu 22.04.5 LTS\")\n", "\n", - "CPU model: AMD Ryzen 9 3950X 16-Core Processor, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n", + "CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n", + "Thread count: 16 physical cores, 16 logical processors, using up to 16 threads\n", "\n", - "Optimize a model with 20 rows, 110 columns and 210 nonzeros\n", + "Optimize a model with 20 rows, 110 columns and 210 nonzeros (Min)\n", "Model fingerprint: 0x1ff9913f\n", + "Model has 10 linear objective coefficients\n", "Variable types: 0 continuous, 110 integer (110 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+02]\n", @@ -159,15 +160,23 @@ "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.01 seconds (0.00 work units)\n", - "Thread count was 32 (of 32 available processors)\n", + "Explored 1 nodes (38 simplex iterations) in 0.00 seconds (0.00 work units)\n", + "Thread count was 16 (of 16 available processors)\n", "\n", "Solution count 4: 2 3 4 5 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.000000000000e+00, best bound 2.000000000000e+00, gap 0.0000%\n", "\n", - "User-callback calls 148, time in user-callback 0.00 sec\n" + "User-callback calls 149, time in user-callback 0.00 sec\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/axavier/Notebooks/[2025-12] Parallel BB Datasets/venv/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" ] } ], @@ -325,13 +334,14 @@ "capacities\n", " [1310. 988. 1004. 1269. 1007.]\n", "\n", - "Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (linux64 - \"Ubuntu 24.04.3 LTS\")\n", + "Gurobi Optimizer version 13.0.0 build v13.0.0rc1 (linux64 - \"Ubuntu 22.04.5 LTS\")\n", "\n", - "CPU model: AMD Ryzen 9 3950X 16-Core Processor, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n", + "CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n", + "Thread count: 16 physical cores, 16 logical processors, using up to 16 threads\n", "\n", - "Optimize a model with 5 rows, 10 columns and 50 nonzeros\n", + "Optimize a model with 5 rows, 10 columns and 50 nonzeros (Min)\n", "Model fingerprint: 0xaf3ac15e\n", + "Model has 10 linear objective coefficients\n", "Variable types: 0 continuous, 10 integer (10 binary)\n", "Coefficient statistics:\n", " Matrix range [2e+01, 1e+03]\n", @@ -355,7 +365,7 @@ " 0 0 -1428.7265 0 4 -1279.0000 -1428.7265 11.7% - 0s\n", "\n", "Explored 1 nodes (4 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 32 (of 32 available processors)\n", + "Thread count was 16 (of 16 available processors)\n", "\n", "Solution count 3: -1279 -995 -804 \n", "No other solutions better than -1279\n", @@ -363,7 +373,7 @@ "Optimal solution found (tolerance 1.00e-04)\n", "Best objective -1.279000000000e+03, best bound -1.279000000000e+03, gap 0.0000%\n", "\n", - "User-callback calls 416, time in user-callback 0.00 sec\n" + "User-callback calls 418, time in user-callback 0.00 sec\n" ] } ], @@ -501,13 +511,14 @@ "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 12.0.3 build v12.0.3rc0 (linux64 - \"Ubuntu 24.04.3 LTS\")\n", + "Gurobi Optimizer version 13.0.0 build v13.0.0rc1 (linux64 - \"Ubuntu 22.04.5 LTS\")\n", "\n", - "CPU model: AMD Ryzen 9 3950X 16-Core Processor, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n", + "CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n", + "Thread count: 16 physical cores, 16 logical processors, using up to 16 threads\n", "\n", - "Optimize a model with 21 rows, 110 columns and 220 nonzeros\n", + "Optimize a model with 21 rows, 110 columns and 220 nonzeros (Min)\n", "Model fingerprint: 0x8d8d9346\n", + "Model has 90 linear objective coefficients\n", "Variable types: 0 continuous, 110 integer (110 binary)\n", "Coefficient statistics:\n", " Matrix range [5e-01, 2e+02]\n", @@ -530,21 +541,21 @@ "H 0 0 153.5000000 0.00000 100% - 0s\n", "H 0 0 131.7700000 0.00000 100% - 0s\n", " 0 0 17.14595 0 10 131.77000 17.14595 87.0% - 0s\n", - "H 0 0 115.6500000 17.14595 85.2% - 0s\n", - "H 0 0 114.5300000 64.28872 43.9% - 0s\n", - "H 0 0 98.3900000 64.28872 34.7% - 0s\n", - " 0 0 74.01104 0 15 98.39000 74.01104 24.8% - 0s\n", - "H 0 0 91.2300000 74.01104 18.9% - 0s\n", + "H 0 0 122.8100000 17.14595 86.0% - 0s\n", + "H 0 0 98.3900000 17.14595 82.6% - 0s\n", + "H 0 0 92.2900000 64.28872 30.3% - 0s\n", + "H 0 0 91.6700000 64.28872 29.9% - 0s\n", + " 0 0 64.28872 0 15 91.67000 64.28872 29.9% - 0s\n", + "H 0 0 91.2300000 64.28872 29.5% - 0s\n", "\n", "Cutting planes:\n", " Cover: 16\n", - " MIR: 1\n", " StrongCG: 1\n", "\n", - "Explored 1 nodes (42 simplex iterations) in 0.02 seconds (0.00 work units)\n", - "Thread count was 32 (of 32 available processors)\n", + "Explored 1 nodes (42 simplex iterations) in 0.01 seconds (0.00 work units)\n", + "Thread count was 16 (of 16 available processors)\n", "\n", - "Solution count 9: 91.23 98.39 114.53 ... 368.79\n", + "Solution count 10: 91.23 91.67 92.29 ... 368.79\n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 9.123000000000e+01, best bound 9.123000000000e+01, gap 0.0000%\n", @@ -678,13 +689,14 @@ "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 12.0.3 build v12.0.3rc0 (linux64 - \"Ubuntu 24.04.3 LTS\")\n", + "Gurobi Optimizer version 13.0.0 build v13.0.0rc1 (linux64 - \"Ubuntu 22.04.5 LTS\")\n", "\n", - "CPU model: AMD Ryzen 9 3950X 16-Core Processor, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n", + "CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n", + "Thread count: 16 physical cores, 16 logical processors, using up to 16 threads\n", "\n", - "Optimize a model with 5 rows, 10 columns and 28 nonzeros\n", + "Optimize a model with 5 rows, 10 columns and 28 nonzeros (Min)\n", "Model fingerprint: 0xe5c2d4fa\n", + "Model has 10 linear objective coefficients\n", "Variable types: 0 continuous, 10 integer (10 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -696,15 +708,15 @@ "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 32 available processors)\n", + "Explored 0 nodes (0 simplex iterations) in 0.00 seconds (0.00 work units)\n", + "Thread count was 1 (of 16 available processors)\n", "\n", "Solution count 1: 213.49 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.134900000000e+02, best bound 2.134900000000e+02, gap 0.0000%\n", "\n", - "User-callback calls 183, time in user-callback 0.00 sec\n" + "User-callback calls 181, time in user-callback 0.00 sec\n" ] } ], @@ -819,13 +831,14 @@ "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 12.0.3 build v12.0.3rc0 (linux64 - \"Ubuntu 24.04.3 LTS\")\n", + "Gurobi Optimizer version 13.0.0 build v13.0.0rc1 (linux64 - \"Ubuntu 22.04.5 LTS\")\n", "\n", - "CPU model: AMD Ryzen 9 3950X 16-Core Processor, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n", + "CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n", + "Thread count: 16 physical cores, 16 logical processors, using up to 16 threads\n", "\n", - "Optimize a model with 5 rows, 10 columns and 28 nonzeros\n", + "Optimize a model with 5 rows, 10 columns and 28 nonzeros (Min)\n", "Model fingerprint: 0x4ee91388\n", + "Model has 10 linear objective coefficients\n", "Variable types: 0 continuous, 10 integer (10 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -837,8 +850,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 32 available processors)\n", + "Explored 0 nodes (0 simplex iterations) in 0.00 seconds (0.00 work units)\n", + "Thread count was 1 (of 16 available processors)\n", "\n", "Solution count 2: -1986.37 -1265.56 \n", "No other solutions better than -1986.37\n", @@ -846,7 +859,7 @@ "Optimal solution found (tolerance 1.00e-04)\n", "Best objective -1.986370000000e+03, best bound -1.986370000000e+03, gap 0.0000%\n", "\n", - "User-callback calls 244, time in user-callback 0.00 sec\n" + "User-callback calls 242, time in user-callback 0.00 sec\n" ] } ], @@ -914,14 +927,12 @@ "id": "ef030168", "metadata": {}, "source": [ - "\n", "### Random instance generator\n", "\n", - "The class [MaxWeightStableSetGenerator][MaxWeightStableSetGenerator] can be used to generate random instances of this problem. The class first samples the user-provided probability distributions `n` and `p` to decide the number of vertices and the density of the graph. Then, it generates a random Erdős-Rényi graph $G_{n,p}$. We recall that, in such a graph, each potential edge is included with probabilty $p$, independently for each other. The class then samples the provided probability distribution `w` to decide the vertex weights.\n", + "The class [MaxWeightStableSetGenerator][MaxWeightStableSetGenerator] can be used to generate random instances of this problem. For each instance, the generator creates a new random Erdős-Rényi graph $G_{n,p}$, where $n$ and $p$ are sampled from user-provided probability distributions `n` and `p`. For each vertex, the generator independently samples the weight $w_v$ from the user-provided probability distribution `w`. To create multiple instances with the same graph structure but different vertex weights, you can use [MaxWeightStableSetPerturber][MaxWeightStableSetPerturber]. This class takes an existing instance and generates new instances by applying random scaling factors to the vertex weights while keeping the graph fixed.\n", "\n", "[MaxWeightStableSetGenerator]: ../../api/problems/#miplearn.problems.stab.MaxWeightStableSetGenerator\n", - "\n", - "If `fix_graph=True`, then all generated instances have the same random graph. For each instance, the weights are decided by sampling `w`, as described above.\n", + "[MaxWeightStableSetPerturber]: ../../api/problems/#miplearn.problems.stab.MaxWeightStableSetPerturber\n", "\n", "### Example" ] @@ -942,49 +953,50 @@ "output_type": "stream", "text": [ "graph [(0, 2), (0, 4), (0, 8), (1, 2), (1, 3), (1, 5), (1, 6), (1, 9), (2, 5), (2, 9), (3, 6), (3, 7), (6, 9), (7, 8), (8, 9)]\n", - "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", + "weights[0] [33.78 94.78 71.97 55.15 14.32 14.33 5.41 82.5 56.7 65.79]\n", + "weights[1] [36. 86.89 68.02 56.08 14.75 15.26 5.35 82.41 57.66 64.06]\n", "\n", "Set parameter PreCrush to value 1\n", - "Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (linux64 - \"Ubuntu 24.04.3 LTS\")\n", + "Gurobi Optimizer version 13.0.0 build v13.0.0rc1 (linux64 - \"Ubuntu 22.04.5 LTS\")\n", "\n", - "CPU model: AMD Ryzen 9 3950X 16-Core Processor, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n", + "CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n", + "Thread count: 16 physical cores, 16 logical processors, using up to 16 threads\n", "\n", "Non-default parameters:\n", "PreCrush 1\n", "\n", - "Optimize a model with 15 rows, 10 columns and 30 nonzeros\n", - "Model fingerprint: 0x3240ea4a\n", + "Optimize a model with 15 rows, 10 columns and 30 nonzeros (Min)\n", + "Model fingerprint: 0xc7a32e6d\n", + "Model has 10 linear objective coefficients\n", "Variable types: 0 continuous, 10 integer (10 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", - " Objective range [6e+00, 1e+02]\n", + " Objective range [5e+00, 9e+01]\n", " Bounds range [1e+00, 1e+00]\n", " RHS range [1e+00, 1e+00]\n", - "Found heuristic solution: objective -219.1400000\n", + "Found heuristic solution: objective -211.0600000\n", "Presolve removed 7 rows and 2 columns\n", "Presolve time: 0.00s\n", "Presolved: 8 rows, 8 columns, 19 nonzeros\n", "Variable types: 0 continuous, 8 integer (8 binary)\n", "\n", - "Root relaxation: objective -2.205650e+02, 5 iterations, 0.00 seconds (0.00 work units)\n", + "Root relaxation: cutoff, 5 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 infeasible 0 -219.14000 -219.14000 0.00% - 0s\n", + " 0 0 cutoff 0 -211.06000 -211.06000 0.00% - 0s\n", "\n", - "Explored 1 nodes (5 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 32 (of 32 available processors)\n", + "Explored 1 nodes (5 simplex iterations) in 0.00 seconds (0.00 work units)\n", + "Thread count was 16 (of 16 available processors)\n", "\n", - "Solution count 1: -219.14 \n", - "No other solutions better than -219.14\n", + "Solution count 1: -211.06 \n", + "No other solutions better than -211.06\n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", - "Best objective -2.191400000000e+02, best bound -2.191400000000e+02, gap 0.0000%\n", + "Best objective -2.110600000000e+02, best bound -2.110600000000e+02, gap 0.0000%\n", "\n", - "User-callback calls 303, time in user-callback 0.00 sec\n" + "User-callback calls 304, time in user-callback 0.00 sec\n" ] } ], @@ -994,6 +1006,7 @@ "from scipy.stats import uniform, randint\n", "from miplearn.problems.stab import (\n", " MaxWeightStableSetGenerator,\n", + " MaxWeightStableSetPerturber,\n", " build_stab_model_gurobipy,\n", ")\n", "\n", @@ -1001,14 +1014,20 @@ "random.seed(42)\n", "np.random.seed(42)\n", "\n", - "# Generate random instances with a fixed 10-node graph,\n", + "# Generate a base instance with a 10-node graph,\n", "# 25% density and random weights in the [0, 100] interval.\n", - "data = MaxWeightStableSetGenerator(\n", + "generator = MaxWeightStableSetGenerator(\n", " w=uniform(loc=0.0, scale=100.0),\n", " n=randint(low=10, high=11),\n", " p=uniform(loc=0.25, scale=0.0),\n", - " fix_graph=True,\n", - ").generate(10)\n", + ")\n", + "base_instance = generator.generate(1)[0]\n", + "\n", + "# Create multiple instances with the same graph but perturbed weights\n", + "perturber = MaxWeightStableSetPerturber(\n", + " w_jitter=uniform(loc=0.9, scale=0.1),\n", + ")\n", + "data = perturber.perturb(base_instance, 10)\n", "\n", "# Print the graph and weights for two instances\n", "print(\"graph\", data[0].graph.edges)\n", @@ -1129,17 +1148,18 @@ "\n", "Set parameter PreCrush to value 1\n", "Set parameter LazyConstraints to value 1\n", - "Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (linux64 - \"Ubuntu 24.04.3 LTS\")\n", + "Gurobi Optimizer version 13.0.0 build v13.0.0rc1 (linux64 - \"Ubuntu 22.04.5 LTS\")\n", "\n", - "CPU model: AMD Ryzen 9 3950X 16-Core Processor, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n", + "CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n", + "Thread count: 16 physical cores, 16 logical processors, using up to 16 threads\n", "\n", "Non-default parameters:\n", "PreCrush 1\n", "LazyConstraints 1\n", "\n", - "Optimize a model with 10 rows, 45 columns and 90 nonzeros\n", + "Optimize a model with 10 rows, 45 columns and 90 nonzeros (Min)\n", "Model fingerprint: 0x719675e5\n", + "Model has 45 linear objective coefficients\n", "Variable types: 0 continuous, 45 integer (45 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -1160,15 +1180,15 @@ "Cutting planes:\n", " Lazy constraints: 3\n", "\n", - "Explored 1 nodes (17 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 32 (of 32 available processors)\n", + "Explored 1 nodes (17 simplex iterations) in 0.00 seconds (0.00 work units)\n", + "Thread count was 16 (of 16 available processors)\n", "\n", "Solution count 1: 2921 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.921000000000e+03, best bound 2.921000000000e+03, gap 0.0000%\n", "\n", - "User-callback calls 111, time in user-callback 0.00 sec\n" + "User-callback calls 113, time in user-callback 0.00 sec\n" ] } ], @@ -1297,6 +1317,14 @@ "If `fix_units=True`, then the list of generators (with their respective parameters) is kept the same for all generated instances. If `cost_jitter` and `demand_jitter` are provided, the instances will still have slightly different costs and demands." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf0b884a", + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "id": "855b87b4", @@ -1307,7 +1335,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 8, "id": "6217da7c", "metadata": { "ExecuteTime": { @@ -1348,13 +1376,14 @@ "demand[1]\n", " [ 827.37 926.76 1166.64 1128.59 939.17 948.8 950.95 639.5 ]\n", "\n", - "Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (linux64 - \"Ubuntu 24.04.3 LTS\")\n", + "Gurobi Optimizer version 13.0.0 build v13.0.0rc1 (linux64 - \"Ubuntu 22.04.5 LTS\")\n", "\n", - "CPU model: AMD Ryzen 9 3950X 16-Core Processor, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n", + "CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n", + "Thread count: 16 physical cores, 16 logical processors, using up to 16 threads\n", "\n", - "Optimize a model with 162 rows, 120 columns and 512 nonzeros\n", + "Optimize a model with 162 rows, 120 columns and 512 nonzeros (Min)\n", "Model fingerprint: 0x1e3651da\n", + "Model has 120 linear objective coefficients\n", "Model has 40 quadratic objective terms\n", "Variable types: 40 continuous, 80 integer (80 binary)\n", "Coefficient statistics:\n", @@ -1382,43 +1411,31 @@ " 0 0 203097.772 0 4 208079.699 203097.772 2.39% - 0s\n", " 0 0 203097.772 0 4 208079.699 203097.772 2.39% - 0s\n", " 0 0 203097.772 0 3 208079.699 203097.772 2.39% - 0s\n", - " 0 0 203097.772 0 3 208079.699 203097.772 2.39% - 0s\n", - " 0 0 203097.772 0 3 208079.699 203097.772 2.39% - 0s\n", - " 0 0 205275.299 0 - 208079.699 205275.299 1.35% - 0s\n", - " 0 0 205777.846 0 2 208079.699 205777.846 1.11% - 0s\n", - " 0 0 205789.407 0 - 208079.699 205789.407 1.10% - 0s\n", - " 0 0 postponed 0 208079.699 205789.407 1.10% - 0s\n", - " 0 0 postponed 0 208079.699 205789.408 1.10% - 0s\n", - " 0 0 205789.408 0 4 208079.699 205789.408 1.10% - 0s\n", - " 0 0 205789.408 0 4 208079.699 205789.408 1.10% - 0s\n", - " 0 0 205789.408 0 1 208079.699 205789.408 1.10% - 0s\n", - " 0 0 205789.408 0 - 208079.699 205789.408 1.10% - 0s\n", - " 0 0 205789.408 0 - 208079.699 205789.408 1.10% - 0s\n", - " 0 0 205789.408 0 - 208079.699 205789.408 1.10% - 0s\n", - " 0 0 205789.408 0 - 208079.699 205789.408 1.10% - 0s\n", - " 0 0 205789.408 0 2 208079.699 205789.408 1.10% - 0s\n", - "H 0 0 207525.63560 205789.408 0.84% - 0s\n", - " 0 0 205789.408 0 2 207525.636 205789.408 0.84% - 0s\n", - " 0 2 205789.408 0 2 207525.636 205789.408 0.84% - 0s\n", - "* 9 0 5 205789.40812 205789.408 0.00% 0.0 0s\n", + " 0 0 203097.772 0 2 208079.699 203097.772 2.39% - 0s\n", + " 0 0 204065.874 0 2 208079.699 204065.874 1.93% - 0s\n", + " 0 0 205410.033 0 2 208079.699 205410.033 1.28% - 0s\n", + " 0 0 205413.331 0 2 208079.699 205413.331 1.28% - 0s\n", + "H 0 0 205789.40802 205458.260 0.16% - 0s\n", + "* 0 0 0 205789.40802 205458.260 0.16% - 0s\n", + " 0 0 - 0 205789.408 205787.978 0.00% - 0s\n", "\n", "Cutting planes:\n", + " Gomory: 1\n", " Cover: 1\n", - " Implied bound: 6\n", - " MIR: 2\n", - " Flow cover: 2\n", - " RLT: 2\n", + " Implied bound: 1\n", + " MIR: 7\n", + " Flow cover: 3\n", + " Relax-and-lift: 1\n", "\n", - "Explored 11 nodes (621 simplex iterations) in 0.32 seconds (0.19 work units)\n", - "Thread count was 32 (of 32 available processors)\n", + "Explored 1 nodes (431 simplex iterations) in 0.02 seconds (0.01 work units)\n", + "Thread count was 16 (of 16 available processors)\n", "\n", - "Solution count 5: 205789 207526 208080 ... 282371\n", - "No other solutions better than 205789\n", + "Solution count 4: 205789 208080 212979 282371 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", - "Best objective 2.057894080889e+05, best bound 2.057894081187e+05, gap 0.0000%\n", + "Best objective 2.057894080182e+05, best bound 2.057879779509e+05, gap 0.0007%\n", "\n", - "User-callback calls 650, time in user-callback 0.00 sec\n" + "User-callback calls 540, time in user-callback 0.00 sec\n" ] } ], @@ -1513,7 +1530,7 @@ "source": [ "### Random instance generator\n", "\n", - "The class [MinWeightVertexCoverGenerator][MinWeightVertexCoverGenerator] can be used to generate random instances of this problem. The class accepts exactly the same parameters and behaves exactly in the same way as [MaxWeightStableSetGenerator][MaxWeightStableSetGenerator]. See the [stable set section](#Stable-Set) for more details.\n", + "The class [MinWeightVertexCoverGenerator][MinWeightVertexCoverGenerator] can be used to generate random instances of this problem. The class accepts the same parameters and behaves in the same way as [MaxWeightStableSetGenerator][MaxWeightStableSetGenerator]. See the [stable set section](#Stable-Set) for more details on the generation process.\n", "\n", "[MinWeightVertexCoverGenerator]: ../../api/problems/#module-miplearn.problems.vertexcover\n", "[MaxWeightStableSetGenerator]: ../../api/problems/#miplearn.problems.stab.MaxWeightStableSetGenerator\n", @@ -1540,13 +1557,14 @@ "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 12.0.3 build v12.0.3rc0 (linux64 - \"Ubuntu 24.04.3 LTS\")\n", + "Gurobi Optimizer version 13.0.0 build v13.0.0rc1 (linux64 - \"Ubuntu 22.04.5 LTS\")\n", "\n", - "CPU model: AMD Ryzen 9 3950X 16-Core Processor, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n", + "CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n", + "Thread count: 16 physical cores, 16 logical processors, using up to 16 threads\n", "\n", - "Optimize a model with 15 rows, 10 columns and 30 nonzeros\n", + "Optimize a model with 15 rows, 10 columns and 30 nonzeros (Min)\n", "Model fingerprint: 0x2d2d1390\n", + "Model has 10 linear objective coefficients\n", "Variable types: 0 continuous, 10 integer (10 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -1566,15 +1584,15 @@ "\n", " 0 0 cutoff 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 32 (of 32 available processors)\n", + "Explored 1 nodes (8 simplex iterations) in 0.00 seconds (0.00 work units)\n", + "Thread count was 16 (of 16 available processors)\n", "\n", "Solution count 1: 301 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 3.010000000000e+02, best bound 3.010000000000e+02, gap 0.0000%\n", "\n", - "User-callback calls 333, time in user-callback 0.00 sec\n" + "User-callback calls 335, time in user-callback 0.00 sec\n" ] } ], @@ -1591,13 +1609,12 @@ "random.seed(42)\n", "np.random.seed(42)\n", "\n", - "# Generate random instances with a fixed 10-node graph,\n", + "# Generate random instances with a 10-node graph,\n", "# 25% density and random weights in the [0, 100] interval.\n", "data = MinWeightVertexCoverGenerator(\n", " w=uniform(loc=0.0, scale=100.0),\n", " n=randint(low=10, high=11),\n", " p=uniform(loc=0.25, scale=0.0),\n", - " fix_graph=True,\n", ").generate(10)\n", "\n", "# Print the graph and weights for two instances\n", @@ -1686,13 +1703,14 @@ "weights[0]: [ 1 1 1 -1 -1 -1 -1 -1 -1 -1 1 -1 -1 1 1 -1 -1]\n", "weights[1]: [-1 1 -1 -1 -1 1 -1 1 -1 1 -1 1 -1 -1 1 -1 1]\n", "\n", - "Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (linux64 - \"Ubuntu 24.04.3 LTS\")\n", + "Gurobi Optimizer version 13.0.0 build v13.0.0rc1 (linux64 - \"Ubuntu 22.04.5 LTS\")\n", "\n", - "CPU model: AMD Ryzen 9 3950X 16-Core Processor, instruction set [SSE2|AVX|AVX2]\n", - "Thread count: 16 physical cores, 32 logical processors, using up to 32 threads\n", + "CPU model: AMD Ryzen 9 7950X 16-Core Processor, instruction set [SSE2|AVX|AVX2|AVX512]\n", + "Thread count: 16 physical cores, 16 logical processors, using up to 16 threads\n", "\n", - "Optimize a model with 0 rows, 10 columns and 0 nonzeros\n", + "Optimize a model with 0 rows, 10 columns and 0 nonzeros (Min)\n", "Model fingerprint: 0x005f9eac\n", + "Model has 5 linear objective coefficients\n", "Model has 17 quadratic objective terms\n", "Variable types: 0 continuous, 10 integer (10 binary)\n", "Coefficient statistics:\n", @@ -1707,8 +1725,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 32 available processors)\n", + "Explored 0 nodes (0 simplex iterations) in 0.00 seconds (0.00 work units)\n", + "Thread count was 1 (of 16 available processors)\n", "\n", "Solution count 2: -3 0 \n", "No other solutions better than -3\n", @@ -1716,7 +1734,7 @@ "Optimal solution found (tolerance 1.00e-04)\n", "Best objective -3.000000000000e+00, best bound -3.000000000000e+00, gap 0.0000%\n", "\n", - "User-callback calls 86, time in user-callback 0.00 sec\n" + "User-callback calls 100, time in user-callback 0.00 sec\n" ] } ], @@ -1770,7 +1788,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/miplearn/problems/stab.py b/miplearn/problems/stab.py index 37ed22c..c1f750a 100644 --- a/miplearn/problems/stab.py +++ b/miplearn/problems/stab.py @@ -32,14 +32,10 @@ class MaxWeightStableSetData: class MaxWeightStableSetGenerator: """Random instance generator for the Maximum-Weight Stable Set Problem. - The generator has two modes of operation. When `fix_graph=True` is provided, - one random Erdős-Rényi graph $G_{n,p}$ is generated in the constructor, where $n$ - and $p$ are sampled from user-provided probability distributions `n` and `p`. To - generate each instance, the generator independently samples each $w_v$ from the - user-provided probability distribution `w`. - - When `fix_graph=False`, a new random graph is generated for each instance; the - remaining parameters are sampled in the same way. + Generates instances by creating a new random Erdős-Rényi graph $G_{n,p}$ for each + instance, where $n$ and $p$ are sampled from user-provided probability distributions + `n` and `p`. For each instance, the generator independently samples each $w_v$ from + the user-provided probability distribution `w`. """ def __init__( @@ -47,7 +43,6 @@ class MaxWeightStableSetGenerator: w: rv_frozen = uniform(loc=10.0, scale=1.0), n: rv_frozen = randint(low=250, high=251), p: rv_frozen = uniform(loc=0.05, scale=0.0), - fix_graph: bool = True, ): """Initialize the problem generator. @@ -66,17 +61,10 @@ class MaxWeightStableSetGenerator: self.w = w self.n = n self.p = p - self.fix_graph = fix_graph - self.graph = None - if fix_graph: - self.graph = self._generate_graph() def generate(self, n_samples: int) -> List[MaxWeightStableSetData]: def _sample() -> MaxWeightStableSetData: - if self.graph is not None: - graph = self.graph - else: - graph = self._generate_graph() + graph = self._generate_graph() weights = np.round(self.w.rvs(graph.number_of_nodes()), 2) return MaxWeightStableSetData(graph, weights) @@ -86,6 +74,42 @@ class MaxWeightStableSetGenerator: return nx.generators.random_graphs.binomial_graph(self.n.rvs(), self.p.rvs()) +class MaxWeightStableSetPerturber: + """Perturbation generator for existing Maximum-Weight Stable Set instances. + + Takes an existing MaxWeightStableSetData instance and generates new instances + by applying randomization factors to the existing weights while keeping the graph fixed. + """ + + def __init__( + self, + w_jitter: rv_frozen = uniform(loc=0.9, scale=0.2), + ): + """Initialize the perturbation generator. + + Parameters + ---------- + w_jitter: rv_continuous + Probability distribution for randomization factors applied to vertex weights. + """ + assert isinstance( + w_jitter, rv_frozen + ), "w_jitter should be a SciPy probability distribution" + self.w_jitter = w_jitter + + def perturb( + self, + instance: MaxWeightStableSetData, + n_samples: int, + ) -> List[MaxWeightStableSetData]: + def _sample() -> MaxWeightStableSetData: + jitter_factors = self.w_jitter.rvs(instance.graph.number_of_nodes()) + weights = np.round(instance.weights * jitter_factors, 2) + return MaxWeightStableSetData(instance.graph, weights) + + return [_sample() for _ in range(n_samples)] + + def build_stab_model_gurobipy( data: Union[str, MaxWeightStableSetData], params: Optional[dict[str, Any]] = None, diff --git a/miplearn/problems/vertexcover.py b/miplearn/problems/vertexcover.py index eeb5a99..2815d8c 100644 --- a/miplearn/problems/vertexcover.py +++ b/miplearn/problems/vertexcover.py @@ -29,9 +29,8 @@ class MinWeightVertexCoverGenerator: w: rv_frozen = uniform(loc=10.0, scale=1.0), n: rv_frozen = randint(low=250, high=251), p: rv_frozen = uniform(loc=0.05, scale=0.0), - fix_graph: bool = True, ): - self._generator = MaxWeightStableSetGenerator(w, n, p, fix_graph) + self._generator = MaxWeightStableSetGenerator(w, n, p) def generate(self, n_samples: int) -> List[MinWeightVertexCoverData]: return [ diff --git a/tests/components/cuts/test_mem.py b/tests/components/cuts/test_mem.py index 207e385..1d70c6d 100644 --- a/tests/components/cuts/test_mem.py +++ b/tests/components/cuts/test_mem.py @@ -28,17 +28,17 @@ def test_mem_component_gp( clf.fit.assert_called() x, y = clf.fit.call_args.args assert x.shape == (3, 50) - assert y.shape == (3, 412) + assert y.shape == (3, 382) y = y.tolist() assert y[0][40:50] == [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - assert y[1][40:50] == [1, 1, 0, 1, 1, 1, 1, 1, 1, 1] - assert y[2][40:50] == [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + assert y[1][40:50] == [1, 1, 1, 0, 1, 1, 1, 1, 1, 1] + assert y[2][40:50] == [1, 1, 1, 1, 0, 0, 1, 1, 1, 1] # Should store violations assert comp.constrs_ is not None assert comp.n_features_ == 50 - assert comp.n_targets_ == 412 - assert len(comp.constrs_) == 412 + assert comp.n_targets_ == 382 + assert len(comp.constrs_) == 382 # Call before-mip stats: Dict[str, Any] = {} @@ -54,7 +54,7 @@ def test_mem_component_gp( model.set_cuts.assert_called() (cuts_aot_,) = model.set_cuts.call_args.args assert cuts_aot_ is not None - assert len(cuts_aot_) == 256 + assert len(cuts_aot_) == 247 def test_usage_stab( diff --git a/tests/fixtures/gen_stab.py b/tests/fixtures/gen_stab.py index 6eb9267..1daba80 100644 --- a/tests/fixtures/gen_stab.py +++ b/tests/fixtures/gen_stab.py @@ -7,6 +7,7 @@ from miplearn.collectors.basic import BasicCollector from miplearn.io import write_pkl_gz from miplearn.problems.stab import ( MaxWeightStableSetGenerator, + MaxWeightStableSetPerturber, build_stab_model_gurobipy, build_stab_model_pyomo, ) @@ -17,9 +18,12 @@ gen = MaxWeightStableSetGenerator( w=uniform(10.0, scale=1.0), n=randint(low=50, high=51), p=uniform(loc=0.5, scale=0.0), - fix_graph=True, ) -data = gen.generate(3) +pr = MaxWeightStableSetPerturber( + w_jitter=uniform(0.9, scale=0.2), +) +base_instance = gen.generate(1)[0] +data = pr.perturb(base_instance, 3) params = {"seed": 42, "threads": 1} diff --git a/tests/fixtures/stab-gp-n50-00000.h5 b/tests/fixtures/stab-gp-n50-00000.h5 index 720476a..2959301 100644 Binary files a/tests/fixtures/stab-gp-n50-00000.h5 and b/tests/fixtures/stab-gp-n50-00000.h5 differ diff --git a/tests/fixtures/stab-gp-n50-00000.mps.gz b/tests/fixtures/stab-gp-n50-00000.mps.gz index c6b6ffb..240ddbc 100644 Binary files a/tests/fixtures/stab-gp-n50-00000.mps.gz and b/tests/fixtures/stab-gp-n50-00000.mps.gz differ diff --git a/tests/fixtures/stab-gp-n50-00000.pkl.gz b/tests/fixtures/stab-gp-n50-00000.pkl.gz index 39d687c..cac54cb 100644 Binary files a/tests/fixtures/stab-gp-n50-00000.pkl.gz and b/tests/fixtures/stab-gp-n50-00000.pkl.gz differ diff --git a/tests/fixtures/stab-gp-n50-00001.h5 b/tests/fixtures/stab-gp-n50-00001.h5 index b72b25d..42a108b 100644 Binary files a/tests/fixtures/stab-gp-n50-00001.h5 and b/tests/fixtures/stab-gp-n50-00001.h5 differ diff --git a/tests/fixtures/stab-gp-n50-00001.mps.gz b/tests/fixtures/stab-gp-n50-00001.mps.gz index 5c9d091..e210686 100644 Binary files a/tests/fixtures/stab-gp-n50-00001.mps.gz and b/tests/fixtures/stab-gp-n50-00001.mps.gz differ diff --git a/tests/fixtures/stab-gp-n50-00001.pkl.gz b/tests/fixtures/stab-gp-n50-00001.pkl.gz index 5b00d9e..f6e7cbf 100644 Binary files a/tests/fixtures/stab-gp-n50-00001.pkl.gz and b/tests/fixtures/stab-gp-n50-00001.pkl.gz differ diff --git a/tests/fixtures/stab-gp-n50-00002.h5 b/tests/fixtures/stab-gp-n50-00002.h5 index e2f0af6..4bb5b3e 100644 Binary files a/tests/fixtures/stab-gp-n50-00002.h5 and b/tests/fixtures/stab-gp-n50-00002.h5 differ diff --git a/tests/fixtures/stab-gp-n50-00002.mps.gz b/tests/fixtures/stab-gp-n50-00002.mps.gz index 3292356..faf3d2a 100644 Binary files a/tests/fixtures/stab-gp-n50-00002.mps.gz and b/tests/fixtures/stab-gp-n50-00002.mps.gz differ diff --git a/tests/fixtures/stab-gp-n50-00002.pkl.gz b/tests/fixtures/stab-gp-n50-00002.pkl.gz index a51419a..3006191 100644 Binary files a/tests/fixtures/stab-gp-n50-00002.pkl.gz and b/tests/fixtures/stab-gp-n50-00002.pkl.gz differ diff --git a/tests/fixtures/stab-pyo-n50-00000.h5 b/tests/fixtures/stab-pyo-n50-00000.h5 index 1d6e921..d28a31c 100644 Binary files a/tests/fixtures/stab-pyo-n50-00000.h5 and b/tests/fixtures/stab-pyo-n50-00000.h5 differ diff --git a/tests/fixtures/stab-pyo-n50-00000.mps.gz b/tests/fixtures/stab-pyo-n50-00000.mps.gz index 6f96752..ac1a78d 100644 Binary files a/tests/fixtures/stab-pyo-n50-00000.mps.gz and b/tests/fixtures/stab-pyo-n50-00000.mps.gz differ diff --git a/tests/fixtures/stab-pyo-n50-00000.pkl.gz b/tests/fixtures/stab-pyo-n50-00000.pkl.gz index 9ba08f1..c73cd1f 100644 Binary files a/tests/fixtures/stab-pyo-n50-00000.pkl.gz and b/tests/fixtures/stab-pyo-n50-00000.pkl.gz differ diff --git a/tests/fixtures/stab-pyo-n50-00001.h5 b/tests/fixtures/stab-pyo-n50-00001.h5 index 902b8d3..d29fb5b 100644 Binary files a/tests/fixtures/stab-pyo-n50-00001.h5 and b/tests/fixtures/stab-pyo-n50-00001.h5 differ diff --git a/tests/fixtures/stab-pyo-n50-00001.mps.gz b/tests/fixtures/stab-pyo-n50-00001.mps.gz index 55127a7..e1070f8 100644 Binary files a/tests/fixtures/stab-pyo-n50-00001.mps.gz and b/tests/fixtures/stab-pyo-n50-00001.mps.gz differ diff --git a/tests/fixtures/stab-pyo-n50-00001.pkl.gz b/tests/fixtures/stab-pyo-n50-00001.pkl.gz index dc77411..6ae9eb3 100644 Binary files a/tests/fixtures/stab-pyo-n50-00001.pkl.gz and b/tests/fixtures/stab-pyo-n50-00001.pkl.gz differ diff --git a/tests/fixtures/stab-pyo-n50-00002.h5 b/tests/fixtures/stab-pyo-n50-00002.h5 index 3b1052b..89b469c 100644 Binary files a/tests/fixtures/stab-pyo-n50-00002.h5 and b/tests/fixtures/stab-pyo-n50-00002.h5 differ diff --git a/tests/fixtures/stab-pyo-n50-00002.mps.gz b/tests/fixtures/stab-pyo-n50-00002.mps.gz index cd5aecc..a1328dc 100644 Binary files a/tests/fixtures/stab-pyo-n50-00002.mps.gz and b/tests/fixtures/stab-pyo-n50-00002.mps.gz differ diff --git a/tests/fixtures/stab-pyo-n50-00002.pkl.gz b/tests/fixtures/stab-pyo-n50-00002.pkl.gz index 5ccd75c..3147fa0 100644 Binary files a/tests/fixtures/stab-pyo-n50-00002.pkl.gz and b/tests/fixtures/stab-pyo-n50-00002.pkl.gz differ