Implement UnitCommitmentPerturber

This commit is contained in:
2025-12-08 15:31:22 -06:00
parent 1d44980a7b
commit 146fb6b615
3 changed files with 179 additions and 97 deletions

View File

@@ -1357,9 +1357,9 @@
"\n",
"First, the user-provided probability distributions `n_units` and `n_periods` are sampled to determine the number of generating units and the number of time steps, respectively. Then, for each unit, the probabilities `max_power` and `min_power` are sampled to determine the unit's maximum and minimum power output. To make it easier to generate valid ranges, `min_power` is not specified as the absolute power level in MW, but rather as a multiplier of `max_power`; for example, if `max_power` samples to 100 and `min_power` samples to 0.5, then the unit's power range is set to `[50,100]`. Then, the distributions `cost_startup`, `cost_prod`, `cost_prod_quad` and `cost_fixed` are sampled to determine the unit's startup, linear variable, quadratic variable, and fixed costs, while the distributions `min_uptime` and `min_downtime` are sampled to determine its minimum up/down-time.\n",
"\n",
"After parameters for the units have been generated, the class then generates a periodic demand curve, with a peak every 12 time steps, in the range $(0.4C, 0.8C)$, where $C$ is the sum of all units' maximum power output. Finally, all costs and demand values are perturbed by random scaling factors independently sampled from the distributions `cost_jitter` and `demand_jitter`, respectively.\n",
"After parameters for the units have been generated, the class then generates a periodic demand curve, with a peak every 12 time steps, in the range $(0.4C, 0.8C)$, where $C$ is the sum of all units' maximum power output.\n",
"\n",
"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."
"To create multiple instances with the same unit structure but different values, you can use `UnitCommitmentPerturber`. This class takes an existing UnitCommitmentData instance and generates new instances by applying randomization factors to the existing costs and demand while keeping the unit characteristics (power limits, minimum up/down-times) fixed. More specifically, the costs are multiplied by random scaling factors sampled from the distribution `cost_jitter`, and the demand is multiplied by random scaling factors sampled from `demand_jitter`."
]
},
{
@@ -1380,7 +1380,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 12,
"id": "6217da7c",
"metadata": {
"ExecuteTime": {
@@ -1401,25 +1401,23 @@
"max_power[0] [218.54 477.82 379.4 319.4 120.21]\n",
"min_uptime[0] [7 6 3 5 7]\n",
"min_downtime[0] [7 3 5 6 2]\n",
"min_power[0] [117.79 245.85 271.85 207.7 81.38]\n",
"cost_startup[0] [3042.42 5247.56 4319.45 2912.29 6118.53]\n",
"cost_prod[0] [ 6.97 14.61 18.32 22.8 39.26]\n",
"cost_prod_quad[0] [0.02 0.0514 0.0592 0.0046 0.0608]\n",
"cost_fixed[0] [170.52 65.05 948.89 965.63 808.4 ]\n",
"cost_startup[0] [3710.99 6283.5 4530.89 3526.6 4859.62]\n",
"cost_prod[0] [ 5.91 11.29 16.72 21.53 34.77]\n",
"cost_prod_quad[0] [0.0233 0.0477 0.0527 0.0047 0.0499]\n",
"cost_fixed[0] [ 196.29 51.21 1179.89 1097.07 686.62]\n",
"demand[0]\n",
" [ 869.31 897.58 1212.29 1124.08 930.48 1012.62 869.31 606.15]\n",
" [ 827.37 926.76 1166.64 1128.59 939.17 948.8 950.95 639.5 ]\n",
"\n",
"min_power[1] [117.79 245.85 271.85 207.7 81.38]\n",
"max_power[1] [218.54 477.82 379.4 319.4 120.21]\n",
"min_uptime[1] [7 6 3 5 7]\n",
"min_downtime[1] [7 3 5 6 2]\n",
"min_power[1] [117.79 245.85 271.85 207.7 81.38]\n",
"cost_startup[1] [3710.99 6283.5 4530.89 3526.6 4859.62]\n",
"cost_prod[1] [ 5.91 11.29 16.72 21.53 34.77]\n",
"cost_prod_quad[1] [0.0233 0.0477 0.0527 0.0047 0.0499]\n",
"cost_fixed[1] [ 196.29 51.21 1179.89 1097.07 686.62]\n",
"cost_startup[1] [3594.78 5571.07 3954.24 2276.77 5540.27]\n",
"cost_prod[1] [ 6.36 16.29 19.58 27.21 38.71]\n",
"cost_prod_quad[1] [0.0162 0.0569 0.0669 0.0047 0.069 ]\n",
"cost_fixed[1] [169.99 65.79 914.51 736.5 649.91]\n",
"demand[1]\n",
" [ 827.37 926.76 1166.64 1128.59 939.17 948.8 950.95 639.5 ]\n",
" [ 783.34 954.21 1262.44 1175.56 980.96 926.35 844.7 559.58]\n",
"\n",
"Gurobi Optimizer version 13.0.0 build v13.0.0rc1 (linux64 - \"Ubuntu 22.04.5 LTS\")\n",
"\n",
@@ -1427,60 +1425,77 @@
"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 (Min)\n",
"Model fingerprint: 0x1e3651da\n",
"Model fingerprint: 0xf8c1c4e3\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",
" Matrix range [1e+00, 5e+02]\n",
" Objective range [7e+00, 6e+03]\n",
" Objective range [6e+00, 6e+03]\n",
" QObjective range [9e-03, 1e-01]\n",
" Bounds range [1e+00, 1e+00]\n",
" RHS range [1e+00, 1e+03]\n",
"Found heuristic solution: objective 282371.35206\n",
"Presolve removed 61 rows and 40 columns\n",
"Found heuristic solution: objective 277606.67683\n",
"Presolve removed 45 rows and 32 columns\n",
"Presolve time: 0.00s\n",
"Presolved: 137 rows, 98 columns, 362 nonzeros\n",
"Presolved: 147 rows, 103 columns, 416 nonzeros\n",
"Presolved model has 40 quadratic objective terms\n",
"Variable types: 58 continuous, 40 integer (40 binary)\n",
"Variable types: 55 continuous, 48 integer (48 binary)\n",
"\n",
"Root relaxation: objective 1.995341e+05, 126 iterations, 0.00 seconds (0.00 work units)\n",
"Root relaxation: objective 1.823746e+05, 145 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 199534.058 0 8 282371.352 199534.058 29.3% - 0s\n",
"H 0 0 212978.80635 199534.058 6.31% - 0s\n",
"H 0 0 208079.69920 199534.058 4.11% - 0s\n",
" 0 0 203097.772 0 16 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 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 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",
" 0 0 182374.559 0 9 277606.677 182374.559 34.3% - 0s\n",
"H 0 0 190641.39953 182374.559 4.34% - 0s\n",
"H 0 0 190346.53552 182374.559 4.19% - 0s\n",
" 0 0 187223.908 0 24 190346.536 187223.908 1.64% - 0s\n",
" 0 0 187223.908 0 5 190346.536 187223.908 1.64% - 0s\n",
" 0 0 187223.908 0 5 190346.536 187223.908 1.64% - 0s\n",
" 0 0 187223.908 0 7 190346.536 187223.908 1.64% - 0s\n",
" 0 0 187223.908 0 - 190346.536 187223.908 1.64% - 0s\n",
" 0 0 187223.908 0 2 190346.536 187223.908 1.64% - 0s\n",
" 0 0 187223.908 0 3 190346.536 187223.908 1.64% - 0s\n",
" 0 0 187223.908 0 - 190346.536 187223.908 1.64% - 0s\n",
" 0 0 188108.163 0 3 190346.536 188108.163 1.18% - 0s\n",
" 0 0 189470.596 0 3 190346.536 189470.596 0.46% - 0s\n",
" 0 0 189884.665 0 3 190346.536 189884.665 0.24% - 0s\n",
" 0 0 190074.608 0 3 190346.536 190074.608 0.14% - 0s\n",
" 0 0 190074.608 0 3 190346.536 190074.608 0.14% - 0s\n",
" 0 0 190088.760 0 3 190346.536 190088.760 0.14% - 0s\n",
" 0 0 190134.867 0 3 190346.536 190134.867 0.11% - 0s\n",
" 0 0 190141.852 0 3 190346.536 190141.852 0.11% - 0s\n",
" 0 0 190141.870 0 3 190346.536 190141.870 0.11% - 0s\n",
" 0 0 190144.673 0 3 190346.536 190144.673 0.11% - 0s\n",
" 0 0 190148.372 0 3 190346.536 190148.372 0.10% - 0s\n",
" 0 0 190155.514 0 3 190346.536 190155.514 0.10% - 0s\n",
" 0 0 190157.727 0 3 190346.536 190157.727 0.10% - 0s\n",
" 0 0 190158.746 0 3 190346.536 190158.746 0.10% - 0s\n",
" 0 0 190159.827 0 3 190346.536 190159.827 0.10% - 0s\n",
" 0 0 190160.144 0 3 190346.536 190160.144 0.10% - 0s\n",
" 0 0 190183.316 0 3 190346.536 190183.316 0.09% - 0s\n",
" 0 0 190190.168 0 3 190346.536 190190.168 0.08% - 0s\n",
" 0 0 190270.542 0 3 190346.536 190270.542 0.04% - 0s\n",
" 0 0 190273.871 0 3 190346.536 190273.871 0.04% - 0s\n",
" 0 0 190317.529 0 3 190346.536 190317.529 0.02% - 0s\n",
" 0 0 190317.932 0 3 190346.536 190317.932 0.02% - 0s\n",
" 0 0 - 0 190346.536 190337.053 0.00% - 0s\n",
"\n",
"Cutting planes:\n",
" Gomory: 1\n",
" Cover: 1\n",
" Implied bound: 1\n",
" MIR: 7\n",
" Flow cover: 3\n",
" Implied bound: 3\n",
" MIR: 5\n",
" Relax-and-lift: 1\n",
"\n",
"Explored 1 nodes (431 simplex iterations) in 0.02 seconds (0.01 work units)\n",
"Explored 1 nodes (1077 simplex iterations) in 0.05 seconds (0.02 work units)\n",
"Thread count was 16 (of 16 available processors)\n",
"\n",
"Solution count 4: 205789 208080 212979 282371 \n",
"Solution count 3: 190347 190641 277607 \n",
"\n",
"Optimal solution found (tolerance 1.00e-04)\n",
"Best objective 2.057894080182e+05, best bound 2.057879779509e+05, gap 0.0007%\n",
"Best objective 1.903465355189e+05, best bound 1.903370533334e+05, gap 0.0050%\n",
"\n",
"User-callback calls 540, time in user-callback 0.00 sec\n"
"User-callback calls 654, time in user-callback 0.00 sec\n"
]
}
],
@@ -1488,14 +1503,14 @@
"import random\n",
"import numpy as np\n",
"from scipy.stats import uniform, randint\n",
"from miplearn.problems.uc import UnitCommitmentGenerator, build_uc_model_gurobipy\n",
"from miplearn.problems.uc import UnitCommitmentGenerator, UnitCommitmentPerturber, build_uc_model_gurobipy\n",
"\n",
"# Set random seed to make example reproducible\n",
"random.seed(42)\n",
"np.random.seed(42)\n",
"\n",
"# Generate a random instance with 5 generators and 8 time steps\n",
"data = UnitCommitmentGenerator(\n",
"# Generate a reference instance with 5 generators and 8 time steps\n",
"generator = UnitCommitmentGenerator(\n",
" n_units=randint(low=5, high=6),\n",
" n_periods=randint(low=8, high=9),\n",
" max_power=uniform(loc=50, scale=450),\n",
@@ -1506,10 +1521,15 @@
" cost_fixed=uniform(loc=0, scale=1_000),\n",
" min_uptime=randint(low=2, high=8),\n",
" min_downtime=randint(low=2, high=8),\n",
")\n",
"reference_instance = generator.generate(1)[0]\n",
"\n",
"# Generate perturbed instances using the reference\n",
"perturber = UnitCommitmentPerturber(\n",
" cost_jitter=uniform(loc=0.75, scale=0.5),\n",
" demand_jitter=uniform(loc=0.9, scale=0.2),\n",
" fix_units=True,\n",
").generate(10)\n",
")\n",
"data = perturber.perturb(reference_instance, 10)\n",
"\n",
"# Print problem data for the two first instances\n",
"for i in range(2):\n",
@@ -1517,7 +1537,6 @@
" print(f\"max_power[{i}]\", data[i].max_power)\n",
" print(f\"min_uptime[{i}]\", data[i].min_uptime)\n",
" print(f\"min_downtime[{i}]\", data[i].min_downtime)\n",
" print(f\"min_power[{i}]\", data[i].min_power)\n",
" print(f\"cost_startup[{i}]\", data[i].cost_startup)\n",
" print(f\"cost_prod[{i}]\", data[i].cost_prod)\n",
" print(f\"cost_prod_quad[{i}]\", data[i].cost_prod_quad)\n",