stab: Implement MaxWeightStableSetPerturber; update tests and docs

This commit is contained in:
2025-12-08 10:54:19 -06:00
parent 7fd88b0a3d
commit a4cb46f73e
23 changed files with 197 additions and 152 deletions

View File

@@ -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,

View File

@@ -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,16 +61,9 @@ 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()
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,

View File

@@ -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 [

View File

@@ -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(

View File

@@ -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}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.