Despacho Economico en Mosel
Despacho Economico en Mosel
Despacho Economico en Mosel
1
Preprint; Published version available at http://community.fico.com/docs/DOC-1365
Abstract
Starting from a basic Unit Commitment problem as published in [3], we develop a fully functional,
running implementation for Xpress Mosel, useable in power market modeling. The constraints of
the model are discussed in details and solutions to left open implementation problems are presented,
guidelines on how to handle data input (from both, databases and Excel), as well as data output (to
Excel) are given.
CONTENTS I
Contents
1 Introduction 1
4 Data Input 20
4.1 General Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.2 Unit Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.3 Period Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
CONTENTS II
5 Data Output 25
5.1 Electricity price . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.2 Postprocessing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.3 Data Output to Excel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
6 Remarks 29
A Excel Functions 31
A.1 Function isExcelDocument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
A.2 Referencing cells in Excel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
A.2.1 Column index conversions in the A1 notation . . . . . . . . . . . . . . . 32
A.3 Function Join . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
1 INTRODUCTION 1
1 Introduction
The deregulation of the energy market in the last two decades attracted the researchers’ attention,
as electricity producers constantly needed to optimize their production to stay competitive. The
raising volatility in energy production, mostly due to the renewable resources, even increases the
potential for optimization and therefore the interest in power market models.
We consider a Unit Commitment (UC) model based on the mixed integer linear program
(MILP) presented in [3], which assumes perfect competition, and as such approximates the typical
oligopolistic power market in deregulated countries. Its main advantage over possibly more
sophisticated game theoretical or stochastic approaches is its capability to handle real-world
problem sizes with an accurate physical model of the power units, combined with its relatively
good comprehensibility.
To make the model in [3] suitable for real-life application, we need to modify it in a few key
aspects. In particular, we focus on
• the reduction of the prohibitively large number of constraints needed to model the start-up
costs,
• the incorporation of daily fuel prices, as fuel costs make up the better part of the production
costs.
After a short theoretical presentation of the model, we put our main focus on a discussion of
the constraints and a thorough guide on their implementation.
Finally, we show how to read the needed input data (which may be considerably huge, if long
time period real world data is used) from either an SQL database or an Excel spreadsheet. The
output of the results is written to an Excel spreadsheet, for further processing and analysis.
We use the Xpress-Mosel modeling language [1], which is part of the Xpress Optimization
Suite [2].
2 THE UNIT COMMITMENT PROBLEM 2
In the remaining of this section we discuss the components of our model in the above listed order
(subsections 2.2 - 2.4), give a short note on how to handle infeasibilities 2.5, and include summaries
of the parameters 2.6 and the model 2.7. The latter may be used as a convenient reference in the
following sections, since it is cross-referenced with the discussion and implementation over there.
In any period k ∈ K, the units j ∈ J are modeled by their operational state (on/off) vjk , their
current production pkj and their maximal possible production pkj
∀ j ∈ J, k ∈ K : vjk ∈ {0, 1}
pkj , pkj ∈ R+ ,
where vjk = 1 iff unit j is operational in period k and may produce power.
2 THE UNIT COMMITMENT PROBLEM 3
The maximal possible production pkj is used to measure the spinning reserve: In case of a power
outage, the power grid must be stabilized by ramping up the currently operational units. The
available additional production capacity pkj − pkj is called spinning reserve of unit j and a general
grid constraint regulates the minimal need of spinning reserve over all units (see subsection 2.3
below).
2.2.2 Minimal Up- and Downtime (constraints 2.7 to 2.10 in subsection 2.7)
Units cannot be started up and shut down arbitrarily. For example, for a coal unit, after being shut
down, an appropriate cool-off time has to be kept.
To model this, minimal up- and downtime parameters UTj and DTj for each unit are given,
denoting the minimal time a unit has to stay operational after a start up, and the minimal cool-off
time after a shutdown, respectively. Since we do not know when a unit has last been started up or
shut down prior to the modeled time range, we further expect to be given the two parameters IUTj
and IDTj , which tell us for how long a unit needs to be operational or shut down initially. Given
these parameters, we model the minimal uptime as
∀ j ∈ J, k ∈ [IUTj ] : vjk = 1
for the remaining periods. Consider the right-hand side vjk − vjk−1 of the latter constraint: it always
lies in {−1, 0, 1}, and is equal to 1 if and only if unit j starts up in period k. Thus, in the start-up
case, the variables vjk+i on the left-hand side are forced to 1, while the constraint is always fulfilled
otherwise. Similarly, the minimal downtime is modeled as
∀ j ∈ J, k ∈ [IDTj ] : vjk = 0
If a unit j is not operational, it may not produce; otherwise both the actual and maximal possible
production level have to lie in a specific interval, defined by the two parameters Pj (minimal
production) and Pj (maximal production). All this can be expressed by
Note that storage units may have a negative minimal production Pj . However, pkj ≥ 0 only captures
power production, while power consumption is modeled by ckj (see section 2.2.5 below).
2 THE UNIT COMMITMENT PROBLEM 4
Of course the production level can not change arbitrarily either. The parameters RUj / RDj denote
the maximal speed when increasing / decreasing the production level of an operating unit. For
example, a unit with RUj = 50MW/h would be able to increase it’s production level from 120MW
to 170MW in one hour, but not to 171MW. This may simply be modeled as
where L is a parameter denoting the period length. However, at start up and shutdown units are
typically able to change their production levels faster. This higher ramping speed is denoted by the
two parameters SUj (maximal production level at start up) and SDj (maximal production level
before shutdown).
Thus for up ramping we get the constraint
The first three terms on the right-hand side ascertain that the production may only increase by
L · RUj if the unit is already running, or by SUj if the unit is starting.
The last term does not change the right hand side in the case vjk = 1, but tightens the constraint
in the case vjk = 0. Consider the constraint without the last term:
Thus, in case of vjk = 0 we could tighten the constraint by subtracting the term min{SUj , pk−1
j +
L · RUj } on the right hand side. However, since we need to multiply the term by (1 − vjk ) to leave
the case vjk = 1 unchanged, to avoid non-linearities, it should not contain a variable. Hence we
have to replace pk−1
j by its best lower bound max{Pj , 0}. This leads exactly to the last term in the
rampup constraint. This term is not necessary for a correct model, but tightens the linear relaxation,
possibly improving the solution time.
The rampdown is modeled analogously:
Finally, we need to put a limit on the possible maximal production in case of an imminent shutdown.
A shutdown scheduled for period k + 1 can not be postponed, even in case of a power outage.
Thus, in case of a shutdown in k + 1, the unit cannot produce more than SDj in period k:
The term SDj (vjk − vjk+1 ) expresses the desired limit, while the term Pj vjk+1 keeps the constraint
valid in each of the other three settings of vjk and vjk+1 .
Storage units are used to even out the demand. The classical examples for storage units are water
pumping stations and, to a lesser extent, batteries. We indicate a storage unit by a negative minimal
production Pj , meaning that the maximal storage inflow is −Pj .
Not being thermal units, the nowadays relevant storage units have very high ramping speeds, which
allow us to neglect them, i.e. we assume RUj = RDj = SDj = SUj = Pj + (−Pj ) for each
storage unit j.
In our model storage units are characterized by five parameters:
The maximal storage inflow and the storage capacity are now easily formulated as
∀ j ∈ J, k ∈ K : skj ≤ SCj
∀ j ∈ J, k ∈ K : ckj ≤ max{0, −Pj }
The storage fill change from period k − 1 to k has to account for the prior storage, the stored and
consumed energy, and a possible constant inflow (for example a stream feeding a reservoir):
The initial and final storage fill parameters SIj and SFj are set outside the model (possibly for
raising the total storage when demands and prizes are expected to increase after the considered
total time period). They apply to the storage fill at k = 1 and k = T + 1:
∀j ∈J : s1j = SIj
∀j ∈J : SFj = sTj + L · (SEj cTj − pTj + SIFj )
2.3 Power Grid Constraints (constraints 2.20 and 2.21 in subsection 2.7)
All units together have to satisfy the energy demand in each period, given as Dk :
X
∀k∈K : (pkj − ckj ) = Dk
j∈J
Moreover, to be failure-tolerant, the units should be able to compensate a power outage, caused
for example by a failing unit. This is expressed by the need to keep a given reserve Rk capacity
available:
X
∀k∈K : (pkj − pkj + ckj ) ≥ Rk
j∈J
Typically Rk is constant over time. However, a dependency e.g. on Dk maybe reasonable, too.
The objective is to minimize the overall costs consisting of production costs cpkj ≥ 0 (see subsection
2.4.2 below) as well as start-up and shutdown costs, cukj , cdkj ≥ 0 (see 2.4.3):
XX
min cpkj + cukj + cdkj .
j∈J k∈K
In [3] a convex production cost function is assumed and approximated by piecewise affine linear
functions. However, since the efficiency of a power unit usually increases with its production level,
we assume a concave production cost function. Fortunately, the increase in efficiency is typically
quite small, allowing a nearly affine linear approximation
with parameters A and B. A can be interpreted as the variable cost, incurred for every additional
MWh of production, and B as the fixed cost, incurred for running the unit for one hour.
2 THE UNIT COMMITMENT PROBLEM 7
In our model we further split the parameters A and B in two parts, one part which is attributed to
the needed fuel, and is thus dependent on the current fuel price, while the other part is attributed to
operation and maintenance, and thus fixed. We expect the fuel price to be given as the parameter
FCkFj , where Fj 1 denotes the fuel type used by unit j.
Therefore, we replace A and B by their parts, the time and fuel-type dependend FAj and
independend FBj fuel needs and the time and fuel-type dependend PAj and independend PBj
costs. This leads to
∀ j ∈ J, k ∈ K : cpkj = FAj · FCkFj + PAj L · pkj
+ FBj · FCkFj + PBj L · vjk .
Note that due to the affine, non-linear cost function, we do not have a constant production efficiency
as modeled in many other papers; instead, the modeled production efficiency increases with the
production and is concave, which is typical for thermal units.
2.4.3 Startup and Shutdown Costs (constraints 2.23 and 2.24 in subsection 2.7)
Every unit incurs costs when starting up or shutting down, the former increasing with the offline
time (e.g. for a thermal unit, the start-up costs are partially attributed to the need for reheating it),
the latter constant.
We expect the start-up cost of unit j after an offline time of t periods to be given as CUtj and
the shutdown costs to be given as a single constant CDj . The start-up and shutdown costs can then
be modeled as
Pt
Here, the term vjk − n=1 vjk−n is 1 exactly if unit j starts up in period k and was offline in
periods [(k − 1)..(k − t)], otherwise it is less or equal to 0. Thus, the latter constraint is equivalent
to
Since we minimize the costs, cukj = CUtj for start-ups in an optimal solution, as intended.
1F may not contain special characters and spaces, see subsection 4.3
j
2 THE UNIT COMMITMENT PROBLEM 8
The number of constraints needed in [3] to describe the start-up cost function is quite substantial.
While all the other constraints amount to about 12|J||K|, the model needs about |J||K|2 constraints
to model the start-up cost function as discussed in the last sections. Of course it is not necessary to
model the start-up cost function for |K| cooldown periods, but only for the first periods, when the
cost changes significally. Still, for typical thermal units the relevant timespan is about 2 to 3 days,
leading to between 48|J||K| and 72|J||K| constraints if we assume hourly periods, for example.
If we want to reduce the number of constraints further, we have to use a single step for a whole
group of cooldown periods, which amounts to assigning the same start-up cost to each of these
periods. As an example, let us have a look at a typical start-up cost function with a fixed cost of
about 70% and a variable cost of about 30%. It is possible to reduce the number of steps from 71
to 9, while maintaining a relative error of less than 5%:
100%
Original startup costs (71 steps)
Thinned out startup costs (9 steps)
70%
0% t
Thus, two questions are to be answered: How should we group the periods together to obtain a
relative error as small as possible, and which start-up cost should we assign to each of these group?
The second question is answered easily: If we are given a group of periods with cooldown
times ta to tb , it is straight-forward to show that the minimal relative error
CUtjb − CUtja
bestError(CUtja , CUtjb ) =
CUtjb + CUtja
is obtained with the step value
2 · CUtja · CUtjb
bestStep(CUtja , CUtjb ) = ,
CUtjb + CUtja
To answer the first question: let STARTUP_TOL be a parameter giving the maximal error tolerance.
Then the periods may be grouped iteratively by starting the first group at t = 1, expanding the
group as long as its relative error is less than the tolerance, and continuing with the next group on
the remaining t’s. In pseudo-code, for given j:
Algorithm 1: startupCostThinning
1 ta , tb ← 1 // Start and end index of current group
2 while ta ≤ |K| do
// Expand the group as long as the next relative error is
less than STARTUP_TOL
3 while tb + 1 ≤ |K| ∧ bestError(CUtja , CUtjb +1 ) < STARTUP_TOL do
4 tb ← tb + 1
5 CUtja ← bestStep(CUtja , CUtjb ) // Calculate optimal step value
6 ∀ t ∈ [(ta +1)..tb ] : CUtj ← 0 // Delete other step values
7 ta , tb ← tb + 1 // Continue with next group
It can be proven that the number of groups produced by this algorithm is the minimal number of
groups needed to fulfill the tolerance requirements.
In most cases the demand not satisfiable by the units within the model is covered by energy
sources which are not part of the model (e.g. by power imports from external markets or backup
units). Therefore it is advisable to soften the demand and reserve constraints by measuring the
underproduction and underreserve and applying a penalty to it. The softened demand and reserve
constraints thus are:
X
∀k∈K : (pkj − ckj ) + P−k = Dk
j∈J
X
∀k∈K : (pkj − pkj + ckj ) + R−
k
≥ Rk
j∈J
Given the two parameters UPP (penalty factor for underproduction) and URP (penalty factor for
underreserve), we replace the objective function by
XX X
min cpkj + cukj + cdkj + UPP · P−k + URP · R−
k
.
j∈J k∈K k∈K
2 THE UNIT COMMITMENT PROBLEM 10
The second most common cause is a demand profile too volatile to be satisfied by the units, or
in other words, units too slow to follow the demand profile. This infeasibility also originates
from energy sources not present in the model. Although the softening against underproduction
is enough to prevent infeasibilities, one may want to penalize overproduction less (in real world,
overproduction may for example be dumped on external markets). So we obtain
X
∀k∈K : (pkj − ckj ) + P−k − P+k = Dk
j∈J
X
∀k∈K : (pkj − pkj + ckj ) + R−
k
≥ Rk
j∈J
There are a few other inconsistencies in the input data which could lead to infeasibilities or
unwanted behavior, namely
All those are errors in the input data, which have to be avoided for meaningful optimization.
2 THE UNIT COMMITMENT PROBLEM 11
1F may not contain special characters and spaces, see subsection 4.3
j
2 THE UNIT COMMITMENT PROBLEM 12
Startup and shutdown costs (discussion in 2.4.3 and 2.4.4, implementation in 3.6):
The option noimplicit disallows implicit variable creation, and thus forces the programmer to
declare each variable. Mosel’s automatic line termination requires us to wrap multi-line formulas
after an operator, therefore we deactivate it with explterm. We check the input data with assert(),
which by default is only active in debug mode; keepassert enables it in all modes.
The parameters T (number of periods) and L (period length) are declared in the data input section
(4), since they need to be known for selecting the data to be read.
The size of CU’s second index set range is unknown, since we do not know in advance how
many different start-up costs are available for each unit. We therefore declare this array as dynamic.
FC has to be a dynamic array too, since the set of fuels is not known in advance. Usually, we
would declare such an array as
FC: dynamic array(Fuels, K) of real;
has an important advantage: This way we may pass individual columns of the matrix, the subarrays
FC(f) to the SQL functions, see subsection 4.3. Conveniently, the elements of FC can still be
accessed as FC(f, k).
3.2 Variables
Again, the index sets of the variables are determined from the used indices. In Mosel, the usual
datatype for variables is mpvar (mathematical programming decision variable). The only exception
is the objective function, which as a linear function is declared as linctr (linear constraint, also
used for linear functions).
As the number of storage units is usually quite small, we declare the storage and consumption
variable arrays as dynamic and create the individual variables only for them, thus saving variables.
declarations
p: array(J, K) of mpvar; ! Production [MW]
p_max: array(J, K) of mpvar; ! Maximal possible production [MW]
v: array(J, K) of mpvar; ! On-Off state [0/1]
s: dynamic array(J, K) of mpvar; ! Storage [MWh]
c: dynamic array(J, K) of mpvar; ! Consumption [MW]
cp: array(J, K) of mpvar; ! Production costs [cost]
cu, cd: array(J, K) of mpvar; ! Startup/shutdown costs [cost]
overallCosts: linctr; ! Overall costs [cost]
p_under, p_over: array(K) of mpvar; ! Under-/overproduction [MW]
r_under: array(K) of mpvar; ! Underreserve [MW]
end-declarations
By default, these variables are constrained to take positive real values. This fits all variables except
the on-off-state v, which should be binary:
forall(j in J, k in K) v(j, k) is_binary;
3 IMPLEMENTING THE MODEL 16
Same as with the storage variables, the storage constraints (2.15 to 2.19) are implemented only
for storage units.
forall(j in J | P_min(j) < 0) do
forall(k in K) do
s(j, k) <= SC(j);
c(j, k) <= maxlist(0, -P_min(j));
end-do
forall(k in 2..T) do
s(j, k) = s(j, k-1) + L * (SE(j)*c(j, k-1) - p(j, k-1) + SIF(j));
end-do
SI(j) = s(j, 1);
SF(j) = s(j, T) + L * (SE(j)*c(j, T) - p(j, T) + SIF(j));
end-do
3 IMPLEMENTING THE MODEL 17
The exists operator is again used to enumerate only relevant indices. Especially the number
of constraints depending on t may be further reduced from considering only a subset (compare
subsection 2.4.4), accepting a loss in accuracy of the start-up costs.
The pseudo-code of the start-up cost thinning can be translated one-to-one into Mosel code:
function bestError(CU_j_ta: real, CU_j_tb: real): real
if(CU_j_ta = 0 and CU_j_tb = 0) then
returned := 0;
else
returned := abs(CU_j_ta - CU_j_tb)/(CU_j_ta + CU_j_tb);
end-if
end-function
3 IMPLEMENTING THE MODEL 18
As noted at the beginning of this section (see 3), we need to use the option keepassert to activate
assert outside of debug mode.
4 DATA INPUT 20
4 Data Input
4.1 General Parameters
We use the two parameters UNITS_SOURCE and PERIODS_SOURCE to store the sources for the unit
parameters and the period data. Depending on whether one wants to access a database or a
spreadsheet, these parameters contain the name of a database table or a spreadsheet.
Once we know where to get the period data, we have to know a start time START and the number
of periods T. Since the datatype datetime can not be used for parameters, START will be a string,
formatted with the standard SQL format YYYY-MM-DD HH:MM:SS (example: June 30th, 2007
at 7:12 PM is written as 2007-06-30 19:12:00). The parameter PERIOD_LENGTH should be given in
seconds.
Summarising, the used parameters are:
parameters
UNITS_SOURCE = "Units.xls"; ! Source of the unit parameters
PERIODS_SOURCE = "Periods.xls"; ! Source of the period data
DSN = "Server=?;Database=?;UID=?;PWD=?;"; ! Example DSN
START = "2009-05-11 00:00:00"; ! Start of the modeled timespan
T = 168; ! Number of modeled periods
L = 1; ! Period length [h]
STARTUP_TOL = 0.05; ! Tolerance for modeling of start-up cost
end-parameters
Important: parameters have to be declared with a default value to define their data type.
• one table with the unit parameters with index set J (all except start-up costs),
• one table with the start-up costs with index sets J and K.
4 DATA INPUT 21
The name of the first table should be given as the parameter UNITS_SOURCE, whereas the second
table should have the same name with an appended “_CU” (in reference to the start-up costs name
CUtj ). The names of the index and of the columns should be the same as in our model.
The Excel spreadsheet is set up in the same way, except that we store each set of units in their
own spreadsheet file. Thus the UNITS_SOURCE parameter now denotes the spreadsheet file, while
the two sheets are always called “Units” and “Units_CU”.
Finally, we need the ODBC data source name (DSN), also called “Connection String”, which
differs with the brand of database server used. We expect the DSN to be given as DSN. Connection
strings can be found in the database’s manual. A collection of connection strings for popular
databases is also available at www.connectionstrings.com.
Now, we can setup the parameters needed by initializations:
declarations
unitsDSN: string; ! ODBC data source name
unitsTable: string; ! Table containing the unit parameter
unitsCUTable: string; ! Table containing the start-up costs
end-declarations
if(isExcelDocument(UNITS_SOURCE)) then
unitsDSN := "mmodbc.excel:skiph;" + UNITS_SOURCE;
unitsTable := "Units";
else
unitsDSN := "mmodbc.odbc:" + DSN;
unitsTable := UNITS_SOURCE;
end-if
unitsCUTable := unitsTable + "_CU";
The function isExcelDocument used here is listed in Appendix A, and works by checking the file
extension.
In the initializations block, we read all unit parameters with index set J from the first table
initializations from unitsDSN
[UT,DT,IUT,IDT,P_min,P_max,RU,RD,SU,SD,SC,SE,SIF,SI,SF,F,FA,FB,PA,PB,CD]
as unitsTable +
"(j,UT,DT,IUT,IDT,P_min,P_max,RU,RD,SU,SD,SC,SE,SIF,SI,SF,F,FA,FB,PA,PB,CD)";
Mosel automatically uses the columns j and k as the unit and period indices. Once the units have
been read, we are able to define the Fuels set:
Fuels := union(j in J) {F(j)};
finalize(Fuels);
4 DATA INPUT 22
Note that expandpath expands the path of PERIODS_SOURCE, since the ODBC driver’s working
directory might be different from ours.
To send an SQL query to the database now, we have to build it up first. The following variables
are used for this:
declarations
periodsIndex: text; ! Index column
reindexedPeriods: text; ! Period source with converted index
periodsColumns: list of text; ! Columns to read
periodsArrays: list of array(K) of real;! Arrays to fill
periodsQuery: text; ! Assembled SQL query
end-declarations
The first column to be read is the period index k. How to derive this index depends on the indexing
of the periods in the database. For our model, we expect the database periods to be indexed by the
column t with data type TIMESTAMP. We also assume the database periods to have the same length
as the model periods, L; data with different period lengths needs to be interpolated beforehand.
Given all these informations, we can derive our index k as
periodsIndex := "{fn FLOOR(((t-{ts ’"+START+"’})*24+0.1)/"+L+")} + 1";
In a fully-fledged database, it would be possible to assign the name k to this converted index, and
to use it by this name. In Excel, this assignment has to be done in a subquery. At the same time,
we have to specify the source of the periods:
reindexedPeriods := "SELECT "+periodsIndex+" as k, *";
if(isExcelDocument(PERIODS_SOURCE)) then
reindexedPeriods += " FROM Periods";
else
reindexedPeriods += " FROM " + PERIODS_SOURCE;
end-if
The subquery reindexedPeriods now represents the periods table, with a converted and renamed
index column k.
With the index settled, we can select our first data arrays for reading: the demand D and the
reserve R.
periodsColumns += ["D", "R"];
periodsArrays += [D, R];
Here, the last statement actually does not copy the D and R arrays. Since they are complex datatypes,
Mosel just copies their references to the list. So, when initialising the arrays of this list, we actually
initialize our original arrays.
The procedure for the fuel costs is similar, but since the FC array is dynamic, we first have to
create the cost subarray for each used fuel type:
forall(f in Fuels) do
create(FC(f));
periodsColumns += ["FC_" + f];
periodsArrays += [FC(f)];
end-do
Finally, we can assemble the SQL query and specify the needed periods:
periodsQuery := "SELECT k, " + join(periodsColumns, ", ");
periodsQuery += " FROM ("+reindexedPeriods+")";
periodsQuery += " WHERE 1 <= k AND k <= " + T;
The join function joins the elements of periodsColumns, its implementation can be found in
Appendix A.
In conclusion, an assembled query for example may equate to
SELECT k, D, R, FC_gas, FC_oil, FC_coal
FROM (
SELECT {fn FLOOR(((t-{ts ’2009-05-11 00:00:00’})*24+0.1)/1)} + 1 as k, *
FROM Periods
)
WHERE 1 <= k AND k <= 84
After the initialization, we have to close the connection to the database server:
SQLdisconnect;
When working with big Excel spreadsheets, this approach is a good alternative to multiple
initializations blocks. Excel reopens the spreadsheet for every initializations block, which
can be quite time consuming. In contrast, when using SQL functions, Excel opens the spreadsheet
just once at SQLconnect.
5 DATA OUTPUT 25
5 Data Output
Same as with the data input sources, the destination file is given as a parameter:
parameters
RESULTS_DEST = "Results.xls"; ! Destination of the results
end-parameters
5.2 Postprocessing
The maximal production and ramping constraints just impose an upper limit on the maximal
possible production. Thus, p_max may actually be lower than the real maximal possible production.
We can fix this by deriving the maximal possible production from the production variables and the
operational state:
declarations
exact_p_max: array(J, K) of real;
end-declarations
forall(j in J, k in K) do
exact_p_max(j, k) := P_max(j).sol * v(j, k).sol;
if(k > 1) then
exact_p_max(j, k) := minlist(exact_p_max(j, k),
p(j, k-1).sol + RU(j) * v(j, k-1).sol
+ SU(j) * (1 - v(j, k-1).sol) + P_max(j) * (1 - v(j, k).sol));
end-if
if(k < T) then
exact_p_max(j, k) := minlist(exact_p_max(j, k),
P_max(j) * v(j, k+1).sol + SD(j) * (v(j, k).sol - v(j, k+1).sol));
end-if
end-do
5 DATA OUTPUT 26
We will output each variable to its own sheet (inside the same file). This allows us to
without having to change the position of the existing variables. Since there are usually more periods
than units, and Excel supports more rows than columns2 , we associate the periods with rows and
the units with columns. Together with the actual data, we have to output the index sets J and K.
Since the period indices are relative to START and L, we will also output the timestamp of each
period.
Now, it would be convenient to implement the output in a separate function, which is to be
called for each variable. Unfortunately, one initializations block per function call would be
needed, reopening the Excel spreadsheet each time. Depending on the size of the spreadsheet,
this may be quite time-consuming. Therefore we use a single initializations block, but still
perform the preparation of each variable in a separate function.
We need two functions: one for variables with index set K, and one for variables index sets J
and K. They both should return a two-dimensional array of text, which subsequently will be
written to an Excel sheet:
declarations
PeriodsSheet = dynamic array(rows: range, singleCol: range) of text;
UnitsPeriodsSheet = dynamic array(rows, cols: range) of text;
end-declarations
2 Excel 2007 or higher: 220 rows vs. 214 columns; prior to Excel 2007: 216 rows vs. 28 columns or less
5 DATA OUTPUT 27
In the body of the createSheet function for variables with index set K, we need to:
1. Fill the first two columns with the index and the starting time of each period:
forall(k in K) do
returned(k + 1, 1) := text(k);
returned(k + 1, 2) := text(datetime(START) + (k-1) * L * 3600);
end-do
3. Fill the space between period indices and titles with the actual data:
forall(k in K) do
returned(k + 1, 3) := text(data(k));
end-do
In the function for variables with index sets J and K, the time series for all units J should be
written to adjacent columns. Since the index set J is not necessarily of the form {1, . . . , |J|}, we
first construct a mapping from J to {1, . . . , |J|}:
declarations
mapping: dynamic array(J) of integer;
end-declarations
forall(j in J) do
mapping(j) := getsize(mapping) + 1;
end-do
The remainder of the function is similiar to the previous function for index set K. We need to:
1. Fill the first two columns with the index and the starting time of each period:
forall(k in K) do
returned(k + 1, 1) := text(k);
returned(k + 1, 2) := text(datetime(START) + (k-1) * L * 3600);
end-do
3. Fill the space between period indices and titles with the actual data:
forall(j in J, k in K) do
returned(k + 1, mapping(j)) := text(data(j, k));
end-do
5 DATA OUTPUT 28
These functions only take real-valued time series; For convenience, we implement them for
mpvar-valued time series as well:
function createSheet(data: array(K) of mpvar, name: string): PeriodsSheet
returned := createSheet(array(k in K) data(k).sol, name);
end-function
To perform the output to Excel, we still need to specify the region to which each variable needs
to be written. For this, we will use the cellRange function, implemented in our ExcelFunctions
package (Appendix A), which generates Excel ranges like [Sheet$A1:Q10].
initializations to "mmodbc.excel:noindex;" + RESULTS_DEST
evaluation of createSheet(v)
as cellRange("v", 1, 1, getsize(J)+2, getsize(K)+1);
evaluation of createSheet(p)
as cellRange("p", 1, 1, getsize(J)+2, getsize(K)+1);
evaluation of createSheet(exact_p_max)
as cellRange("p_max", 1, 1, getsize(J)+2, getsize(K)+1);
evaluation of createSheet(price, "Price")
as cellRange("price", 1, 1, 3, getsize(K)+1);
evaluation of createSheet(s)
as cellRange("s", 1, 1, getsize(J)+2, getsize(K)+1);
evaluation of createSheet(c)
as cellRange("c", 1, 1, getsize(J)+2, getsize(K)+1);
evaluation of createSheet(cp)
as cellRange("cp", 1, 1, getsize(J)+2, getsize(K)+1);
evaluation of createSheet(cu)
as cellRange("cu", 1, 1, getsize(J)+2, getsize(K)+1);
evaluation of createSheet(cd)
as cellRange("cd", 1, 1, getsize(J)+2, getsize(K)+1);
end-initializations
6 REMARKS 29
6 Remarks
This whitepaper presents a detailled model for the Unit Commitment problem. We have given its
practical implementation and discussed how to reduce the number of start-up cost constraints and
how to analyze the cause of infeasibilities. Finally, we have shown how to manage data input and
output, and have given a basic estimate of the electricity price.
One major drawback of the implemented model is the lacking consideration of market power.
To remedy this, usually a so-called “uplift” function is added to the model’s electricity price, which
is derived by statistically analyzing the disagreement between the outcome of this model and the
real price on historical data.
Of course, it would be better to consider the market power directly as part of the model.
Different approaches in achieving this are discussed in [5]. The two most popular ones are game
theoretical approaches:
They differ in the modelling of the strategies of the power generating companies. While in a
Cournot model a company may only decide on it’s power output, a company’s strategy in a Supply
Function equilibrium is described by a function mapping the market price to its power supply. The
greater freedom in the choice of a strategy, however, comes at the expense of a higher computational
effort, which in turn forces the use of less detailled models.
For a comparison of the practical performance of Cournot and Supply Function models on the
German power market, we refer to [6].
The second drawback is the neglection of the underlying power grid. The production schedule
resulting from our model may not be feasible on a real power grid, i.e. the power grid may not be
able to transport the electricity from the producing units to the consumers. While this was typically
not a fundamental issue so far, it becomes more and more important due to the increasing energy
production from renewable, more volatile resources causing bottlenecks in the power exchange
beetween different regions and thus necessitate an explicit modelling of the power grid.
Also, most of today’s power markets are connected to one or more neighboring markets through
interconnectors. The interconnectors are used to transport energy from a market with higher price
to a market with lower price, thus diminishing the price difference. The effective price change is
typically small due to the small capacity of the interconnectors, but may not be negligible.
REFERENCES 30
References
[1] FICO Xpress-Mosel. http://www.fico.com/en/Products/DMTools/xpress-
overview/Pages/Xpress-Mosel.aspx.
[3] M. Carrión and J.M. Arroyo. A computationally efficient mixed-integer linear formulation for
the thermal unit commitment problem. Power Systems, IEEE Transactions on, 21(3):1371–
1378, 2006.
[4] S. Heipcke. Using odbc and other database interfaces with mosel. 2009. http://www.
fico.com/en/wp-content/secure_upload/Xpress_moselodbc.pdf.
[5] M. Ventosa, A. Baillo, A. Ramos, and M. Rivier. Electricity market modeling trends. Energy
policy, 33(7):897–913, 2005.
[6] B. Willems, I. Rumiantseva, and H. Weigt. Cournot versus supply functions: What does the
data tell us? Energy Economics, 31(1):38–47, 2009.
A EXCEL FUNCTIONS 31
A Excel Functions
The Excel functions are implemented as part of the ExcelFunctions package. Therefore, the
functions to be used in other packages or the main model have to marked as public.
Unfortunately, we also have to implement the strtolower function for ourselfs, which converts a
string to lower case:
public function strtolower(str: text): text
returned := str;
forall(i in 1..getsize(returned)) do
if(65(! = A !) <= getchar(returned, i) and
getchar(returned, i) <= 90(! = Z !)) then
setchar(returned, i, getchar(returned, i) + (97-65));
end-if
end-do
end-function
While the R1C1 notation is easier to use programmatically, the A1 notation may be more familiar
to end users. Therefore, we offer the boolean useA1notation which switches from R1C1 to A1
notation:
public declarations
useA1notation: boolean; ! If true, the A1 notation is used
end-declarations
Depending on this setting, the coordinates of a cell can be determined with a call to cellCoords:
A EXCEL FUNCTIONS 32
When referencing Excel cells through ODBC, it is usual to include the cell’s sheet name too. This
is done by prepending the sheet name to the coordinates, divided by a dollar sign, and by enclosing
the reference in square brackets. The two following functions generate such references to a cell or
respectively to a cell range:
public function cell(sheet: string, column: integer, row: integer): string
returned := "[" + sheet + "$" + cellCoords(column, row) + "]";
end-function
public function cellRange(sheet: string, column: integer, row: integer,
width: integer, height: integer): string
returned := "["+sheet+"$"+cellRangeCoords(column, row, width, height) + "]";
end-function
The column numbering scheme in the A1 notation is a bit unusual and needs special conversion
functions.
Conversion from integer to A1 column index (1 → A, 2 → B, ...)
public function columnToA1(index: integer): string
declarations
c: text; ! Column name as text (needs mmsystem)
end-declarations
while(index > 0) do
c := " " + c;
setchar(c, 1, 65 (! = A !) + ( (index-1) mod 26));
index := (index-1) div 26;
end-do
returned := string(c);
end-function