summaryrefslogtreecommitdiff
path: root/glpk-5.0/examples/powpl25h.mod
blob: bdeefb9c3ab519351517682b7f723cc594a93e7e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/* Power plant LP scheduler, example data with 25hrs for daylightsavings */

/* Implemented, inspected, written and converted to GNU MathProg
   by NASZVADI, Peter, 199x-2017 <vuk@cs.elte.hu> */

/*
   Fast electric power plant scheduler implementation based on new
   results in author's Thesis.

   The base problem is:
   * given some power plants
   * a short time scale partitioned to equidistant intervals
   * the task is to yielding the cheapest schedule for the plants
   * the daily demand forecast is usually accurate and part of the input

   The power plants has technical limitations:
   * upper and lower bounds of produced energy
   * and also a gradient barrier in both directions
     (can depend on time, but this GMPL implementation is simplified)
   * Also units with same properties (technical and price) should be
     scheduled together usually with near same performance values
   * Assumed a simplified network topology, which is contractive, so
     keeping Kirchhoff's laws is a necessary constraint too
   * All solutions must be integer

   The LP relaxation is equivalent with the MIP problem due to the
   model's matrix interesting property: it is Totally Unimodular
   (proven in 2004 by author) and also a Network Matrix (2006,
   presented at OTDK 2016, Szeged, Hungary) so:
   * it is strictly polynomial if it is solved by most simplex algs
   * all base solutions become integer if the RHS vector is integer
     (it is in real life, so this is an acceptable assumption)
   * The transposed matrix is NOT a Network Matrix in most cases!

   However, adding several other constraints easily turns the problem
   to be NP-hard, which is also pinpointed and discussed in the Thesis.

   See more about electric power plants' scheduling in the
   author's Thesis (in Hungarian):
   http://www.cs.elte.hu/matdiploma/vuk.pdf

   It is possible to run with custom parameters, what is needed
   to define is:
   * TIME set (daylightsaving cases or other than hour intervals)
   * PLANTS set (the 'Demand' is mandatory and usually negative)
   * PRICE parameter (can be negative if energy is sold to a consumer)
   * BOUND parameter (technical bounds)
   * MAXGRAD parameter (technical bounds)

   Then generate a pretty-printed solution by typing:
   glpsol --math powpl25h.mod
*/

set TIME, default {
    '00:00', '01:00', '02:00', '03:00', '04:00',
    '05:00', '06:00', '07:00', '08:00', '09:00',
    '10:00', '11:00', '12:00', '13:00', '14:00',
    '15:00', '16:00', '17:00', '18:00', '19:00',
    '20:00', '21:00', '22:00', '23:00', '24:00'
};
/* Time labels, assumed natural ordering. daylightsaving's bias
   can be inserted p.ex. in Central Europe like:
   ... '01:00', '02:00', '02:00b', '03:00', ... */

set TADJ := setof{(r, s) in TIME cross TIME: r < s}(r, s) diff
    setof{(t, u, v) in TIME cross TIME cross TIME: t < u and u < v}(t, v);
/* Tricky adjacent time label generator because GMPL lacks order determination
   of set elements (except intervals composed of equidistant numbers) */

set PLANTS, default {'Demand'};
/* Demand is a promoted, mandatory one, usually filled
   with negative MW values in data section */

set DIRECTION, default {'Up', 'Down'};
/* All possible directions of gradients, do not touch */

param MAXINT, default 10000;
/* A "macro" for bounding absolute value of all used numbers
   and used as default value */

param PRICE{PLANTS}, default MAXINT;
/* Should be specified in data section, self-explanatory.
   can be negative if there are energy buyers */

param BOUND{(p, t, d) in PLANTS cross TIME cross DIRECTION},
    default if t = '00:00' then if d = 'Down' then BOUND[p, t, 'Up'] else 0 else
        if p <> 'Demand' or d = 'Up' then sum{(u, v) in TADJ: v = t} BOUND[p, u, d]
        else BOUND[p, t, 'Up'];
/* Obvious, technical bounds of each power plant unit (real or virtual like
   'Demand'). If some parts are not given in data section, calculated
   from preceeding values. Also for time '00:00', its 'Down' values by
   default are the same as denoted with 'Up' */

param MAXGRAD{(p, d) in PLANTS cross DIRECTION}, default MAXINT;
/* Usually nonnegative integer, might differ in distinct directions per unit
   in the cited thesis, it is allowed to gradient bounds to depend on time,
   but this is a simplified model */

var x{(t, p) in TIME cross PLANTS}, <= BOUND[p, t, 'Up'], >= BOUND[p, t, 'Down'];
/* The schedule, dimension is MW */

s.t. kirchhoff{t in TIME: t <> '00:00'}: sum{p in PLANTS} x[t, p] = 0;
/* Conservative property */

s.t. gradient{(p, t, u) in PLANTS cross TADJ}:
    -MAXGRAD[p, 'Down'] <= x[t, p] - x[u, p] <= MAXGRAD[p, 'Up'];
/* Technical limitations, each unit usually cannot change performance
   arbitrarily in a short time, so limited in both directions per time unit*/

minimize obj: sum{(t, p) in TIME cross PLANTS}(x[t, p] * PRICE[p]);
/* The objective is the cost of the schedule */

solve;

/* Pretty print solution in table */

printf '+--------+';
for{p in PLANTS}{
       printf '-% 6s-+', '------';
}
printf '\n';
printf '|%7s |', ' ';
for{p in PLANTS}{
       printf ' % 6s |', p;
}
printf '\n';
printf '+--------+';
for{p in PLANTS}{
       printf '-% 6s-+', '------';
}
printf '\n';
for{t in TIME}{
    printf '|%7s |', t;
    for{p in PLANTS}{
        printf ' % 6s |', x[t, p].val;
    }
    printf '\n';
}
printf '+--------+';
for{p in PLANTS}{
       printf '-% 6s-+', '------';
}
printf '\n';

data;

set TIME :=
    '00:00', '01:00', '02:00', '02:00b', '03:00', '04:00',
    '05:00', '06:00', '07:00', '08:00', '09:00',
    '10:00', '11:00', '12:00', '13:00', '14:00',
    '15:00', '16:00', '17:00', '18:00', '19:00',
    '20:00', '21:00', '22:00', '23:00', '24:00';

/*
   Generated random default values and names, the demand is the sum of
   2 sinewaves.
   Also specified a treshold for nuclear plants from 15:00 till 19:00
   The sun is shining only morning and in the afternoon: 07:00-18:00, so
   solar plant cannot produce electric energy after sunset.

   Only touch this section, or export it to a data file!
*/

set PLANTS 'Demand', 'Atom1', 'Atom2', 'Coal', 'Gas1', 'Gas2', 'Green', 'Oil', 'Solar', 'Dam';

param PRICE :=
    'Demand'   0
    'Atom1'    2
    'Atom2'    2
    'Coal'  15.6
    'Gas1'    12
    'Gas2'  11.5
    'Green'  8.8
    'Oil'   23.3
    'Solar'  7.6
    'Dam'      3;
/* price per MW */

param BOUND :=
    [*, *, 'Up']   (tr): 'Atom1' 'Atom2' 'Coal' 'Gas1' 'Gas2' 'Green' 'Oil' 'Solar' 'Dam' :=
        '00:00'             240     240    100    150    150      20    90       0    20
        '01:00'             240     240    155    192    208      35   230       0    20
    [*, *, 'Up']   (tr): 'Atom1' 'Atom2' :=
        '15:00'             200     200
        '19:00'             235     235
    [*, *, 'Up']   (tr): 'Solar' :=
        '07:00'              20
        '18:00'               0
    [*, *, 'Down'] (tr): 'Atom1' 'Atom2' 'Coal' 'Gas1' 'Gas2' 'Green' 'Oil' 'Solar' 'Dam' :=
        '01:00'             100     100     50     62     68       0    75       0    20
    [*, *, 'Up'] : '01:00' '02:00' '02:00b' '03:00' '04:00' '05:00' '06:00' '07:00' '08:00' :=
        'Demand'     -868    -851    -842    -837    -791    -887    -912   -1046   -1155
    [*, *, 'Up'] : '09:00' '10:00' '11:00' '12:00' '13:00' '14:00' '15:00' '16:00' :=
        'Demand'     -945    -873    -797    -990   -1241   -1134    -815    -782
    [*, *, 'Up'] : '17:00' '18:00' '19:00' '20:00' '21:00' '22:00' '23:00' '24:00' :=
        'Demand'     -772    -827    -931   -1105   -1215   -1249   -1183    -952;

param MAXGRAD (tr)
    :      'Atom1' 'Atom2' 'Coal' 'Gas1' 'Gas2' 'Green' 'Oil' 'Solar' 'Dam' :=
    'Up'       30      30     35     89     95       5    56       2     4
    'Down'     30      30     45     96    102       5    56       2     4;

end;