Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly account for Delta_T in objective function terms #41

Merged
merged 2 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@ Change history for MOST
since 1.2
---------

#### 10/25/23
- Fix [issue #39][10] in which the value of `mdi.Delta_T`, the number of
hours represented by each period, was not being accounted for in most
of the terms in the objective function.
*Thanks to Stefano Nicolin.*

#### 10/4/23
- Fix [issue #37][9] which caused a fatal error in storage input checks
with multiple storage units under some circumstances.
*Thanks to Keir Steegstra.*

#### 2/3/23
- Remove extra column in ExpectedRampCost and ignore for single period.
- Remove extra column in mdo.results.ExpectedRampCost and ignore for
single period.


Version 1.2 - *Dec 13, 2022*
Expand Down Expand Up @@ -310,3 +317,4 @@ Version 1.0 - *Jun 1, 2016*
[7]: https://arxiv.org/abs/2204.08140
[8]: https://github.com/MATPOWER/most/issues/29
[9]: https://github.com/MATPOWER/most/issues/37
[10]: https://github.com/MATPOWER/most/issues/39
18 changes: 10 additions & 8 deletions docs/src/MOST-manual/MOST-manual.tex
Original file line number Diff line number Diff line change
Expand Up @@ -858,30 +858,30 @@ \subsubsection{Objective Function}
\item[--] expected cost of active power dispatch and redispatch
\end{itemize}
\begin{align}
f_p(p,p_+,p_-) &= \sum_{t\in T} \sum_{j\in J^t} \sum_{k\in K^{tj}} \psi_\alpha^{tjk} \sum_{i\in I^{tjk}}
f_p(p,p_+,p_-) &= \Delta \cdot \sum_{t\in T} \sum_{j\in J^t} \sum_{k\in K^{tj}} \psi_\alpha^{tjk} \sum_{i\in I^{tjk}}
\Bigl[\widetilde{C}_P^{ti}(p^{tijk}) + C_{P+}^{ti}(p_+^{tijk}) + C_{P-}^{ti}(p_-^{tijk}) \Bigr]
\label{eq:most_energy_cost}
\end{align}
\begin{itemize}
\item[--] cost of zonal reserves\footnotemark[\value{footnote}]
\begin{equation}
f_z(r_z) = \sum_{t\in T} \sum_{j\in J^t} \sum_{k\in K^{tj}} \psi_\alpha^{tjk} \sum_{i\in I^{tjk}} C_z^{ti}(r_z^{tijk})
f_z(r_z) = \Delta \cdot \sum_{t\in T} \sum_{j\in J^t} \sum_{k\in K^{tj}} \psi_\alpha^{tjk} \sum_{i\in I^{tjk}} C_z^{ti}(r_z^{tijk})
\label{eq:most_zres_cost}
\end{equation}
\item[--] cost of endogenous contingency reserves\footnotemark[\value{footnote}]
\begin{equation}
f_r(r_+, r_-) = \sum_{t\in T} \gamma^t \sum_{i\in I^t} \left[C_{R+}^{ti}(r_+^{ti}) + C_{R-}^{ti}(r_-^{ti}) \right]
f_r(r_+, r_-) = \Delta \cdot \sum_{t\in T} \gamma^t \sum_{i\in I^t} \left[C_{R+}^{ti}(r_+^{ti}) + C_{R-}^{ti}(r_-^{ti}) \right]
\label{eq:most_cres_cost}
\end{equation}
\item[--] expected cost of load-following ramping (wear and tear)
\begin{equation}
f_\delta(p) = \sum_{t\in T} \! \gamma^t \!\!\!\!\! \sum_{\begin{aligned}\scriptstyle j_1 &\scriptstyle \in J^{t-1}\\[-6pt] \scriptstyle j_2 &\scriptstyle \in J^t\end{aligned}} \!\!\!\!\!
f_\delta(p) = \Delta \cdot \sum_{t\in T} \! \gamma^t \!\!\!\!\! \sum_{\begin{aligned}\scriptstyle j_1 &\scriptstyle \in J^{t-1}\\[-6pt] \scriptstyle j_2 &\scriptstyle \in J^t\end{aligned}} \!\!\!\!\!
\phi^{t j_2 j_1} \!\!\!\! \sum_{i\in I^{tj_2 0}} \!\!\! C_\delta^i(p^{tij_20} - p^{(t-1)ij_10})
\label{eq:rampcost}
\end{equation}
\item[--] cost of load-following ramp reserves
\begin{equation}
f_{\rm lf}(\delta_+, \delta_-) = \sum_{t\in T} \gamma^t \sum_{i\in I^t} \left[C_{\delta+}^{ti}(\delta_+^{ti}) + C_{\delta-}^{ti}(\delta_-^{ti}) \right]
f_{\rm lf}(\delta_+, \delta_-) = \Delta \cdot \sum_{t\in T} \gamma^t \sum_{i\in I^t} \left[C_{\delta+}^{ti}(\delta_+^{ti}) + C_{\delta-}^{ti}(\delta_-^{ti}) \right]
\label{eq:most_rampres_cost}
\end{equation}
\item[--] cost of initial stored energy and value (since it is negative) of expected leftover stored energy in terminal states
Expand All @@ -890,7 +890,7 @@ \subsubsection{Objective Function}
\end{equation}
\item[--] no load, startup and shutdown costs
\begin{equation}
f_{\rm uc}(u,v,w) = \sum_{t\in T} \gamma^t \sum_{i\in I^t} \Bigl( C_P^{ti}(0) u^{ti} + C_v^{ti} v^{ti} + C_w^{ti} w^{ti} \Bigr)
f_{\rm uc}(u,v,w) = \sum_{t\in T} \gamma^t \sum_{i\in I^t} \Bigl( \Delta \cdot C_P^{ti}(0) u^{ti} + C_v^{ti} v^{ti} + C_w^{ti} w^{ti} \Bigr)
\end{equation}
\end{itemize}

Expand Down Expand Up @@ -1469,7 +1469,7 @@ \subsubsection{{\tt xgd} -- Extra Generator Data ({\tt xGenData})}
\item [*] {All fields are $n_g \times 1$ vectors of per-generator values.}
\item [\dag] {These are defaults provided by \code{loadxgendata}. If \code{gen} is provided, either directly or as the \code{gen} field of \mpc{}, then \code{P = gen(:, PG)}, \code{C = gen(:, GEN\_STATUS)} and \code{R = 2*(gen(:, PMAX) - MIN(0, gen(:, PMIN)))}, otherwise $\code{C}~=~1$, $\code{R}~=~0$ and no default is provided for \code{P} (corresponding field is not optional).}
\item [\dag\dag] {Ramping costs/restrictions from the initial dispatch at $t=0$ are ignored for single-period problems.}
\item [\ddag] {Each of these costs $C(\cdot)$ is presented in the formulation as a general function, but is implmented as a simple linear function of the form $C(x) = a x$, where the linear coefficient being supplied is $a$. The only exception is the ramping cost, which has the quadratic form $C_\delta^i(x) = a x^2$.}
\item [\ddag] {Each of these costs $C(\cdot)$ is presented in the formulation as a general function, but is implmented as a simple linear function of the form $C(x) = a x$, where the linear coefficient being supplied is $a$. The only exception is the ramping cost, which has the quadratic form $C_\delta^i(x) = \frac{1}{\Delta^2} a x^2$.}
\item [\S] {Requires that \code{CommitKey} be present and non-empty.}
\item [\P] {Sign is based on \code{C}\tnote{\dag}, i.e. $+\infty$ for \code{C} = 1, $-\infty$ for \code{C} = 0.}
\end{tablenotes}
Expand Down Expand Up @@ -1722,7 +1722,7 @@ \subsubsection{Input Data}
\code{mpc} & I & & base system data, standard \matpower{} case struct\tnote{\ddag}, with \baseMVA{}, \bus{}, \gen{}, \branch{} and \gencost{} fields \\
\code{offer(t)} & I & & struct with offer data for period~$t$, see Table~\ref{tab:md_inputoffer} for details of sub-fields \\
\code{OpenEnded} & I & 1 & ignore terminal dispatch ramp constraints, \emph{deprecated} \\
\code{RampWearCostCoeff(i,t)} & I & 0 & $n_g \times n_t$, cost of ramping of generator~$i$ from period~$t-1$ to $t$, coefficient $C_\delta^i$ for square of dispatch difference in \eqref{eq:rampcost} \\
\code{RampWearCostCoeff(i,t)} & I & 0 & $n_g \times n_t$, cost of ramping of generator~$i$ from period~$t-1$ to $t$, coefficient $C_\delta^i$ for square of dispatch difference in \eqref{eq:rampcost}\tnote{\S} \\
\code{Storage} & B & & struct with parameters for storage units, see Table~\ref{tab:md_inputstorage} for the input fields \\
\code{TerminalPg(i)} & I & & $n_g \times 1$, injection of generator~$i$ at $t = n_t$, \emph{deprecated, untested} \\
\code{tstep(t)} & B & & $n_t \times 1$ struct of parameters related to period~$t$ \\
Expand All @@ -1736,6 +1736,7 @@ \subsubsection{Input Data}
\item [*] {I = input, O = output, B = both, opt = taken from \matpower{} options.}
\item [\dag] {See Section~\ref{sec:contab} for details. Note that, while \code{loadmd} assigns the same \code{contab} to all $t$ and $j$, it is possible to set different \code{contab} values manually and they will be respected by \code{most}.}
\item [\ddag] {See Appendix~\ref{MUM-app:caseformat} in the \mum{} for details.}
\item [\S] {More precisely, $C_\delta^i(x) = \frac{1}{\Delta^2} a x^2$}, where $a$ is the corresponding value of \code{RampWearCostCoeff(i,t)}.
\end{tablenotes}
\end{threeparttable}
\end{table}
Expand Down Expand Up @@ -3441,6 +3442,7 @@ \subsubsection*{Changes}

\subsubsection*{Bugs Fixed}
\begin{itemize}
\item Fix issue \#39 in which the value of \code{mdi.Delta\_T}, the number of hours represented by each period, was not being accounted for in most of the terms in the objective function. \emph{Thanks to Stefano Nicolin.}
\item Fix issue \#37 which caused a fatal error in storage input checks with multiple storage units under some circumstances. \emph{Thanks to Keir Steegstra.}
\end{itemize}

Expand Down
33 changes: 16 additions & 17 deletions lib/most.m
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,8 @@
% MDO MOST data structure, output
% (see MOST User's Manual for details)


% MOST
% Copyright (c) 2010-2022, Power Systems Engineering Research Center (PSERC)
% Copyright (c) 2010-2023, Power Systems Engineering Research Center (PSERC)
% by Carlos E. Murillo-Sanchez, PSERC Cornell & Universidad Nacional de Colombia
% and Ray Zimmerman, PSERC Cornell
%
Expand All @@ -82,7 +81,7 @@
fprintf( ' ----- Built on MATPOWER -----\n');
fprintf( ' by Carlos E. Murillo-Sanchez, Universidad Nacional de Colombia--Manizales\n');
fprintf( ' and Ray D. Zimmerman, Cornell University\n');
fprintf( ' (c) 2012-2022 Power Systems Engineering Research Center (PSERC) \n');
fprintf( ' (c) 2012-2023 Power Systems Engineering Research Center (PSERC) \n');
fprintf( '=============================================================================\n');
end

Expand Down Expand Up @@ -509,7 +508,7 @@
for k = 1:mdi.idx.nc(t,j)+1
mpc = mdi.flow(t,j,k).mpc;
c00tjk = totcost(mpc.gencost, zeros(ng,1));
mdi.UC.c00(:, t) = mdi.UC.c00(:, t) + mdi.CostWeightsAdj(k, j, t) * c00tjk;
mdi.UC.c00(:, t) = mdi.UC.c00(:, t) + mdi.Delta_T * mdi.CostWeightsAdj(k, j, t) * c00tjk;
c0col = COST + mpc.gencost(:,NCOST) - 1;
ipoly = find(mpc.gencost(:, MODEL) == POLYNOMIAL);
ipwl = find(mpc.gencost(:, MODEL) == PW_LINEAR);
Expand Down Expand Up @@ -1723,19 +1722,19 @@
om.init_indexed_name('qdc', 'RampWear', {nt+1, nj_max, nj_max});
end
for j = 1:mdi.idx.nj(1)
w = mdi.tstep(1).TransMat(j,1); % the probability of going from initial state to jth
Q = spdiags(w * baseMVA^2 * mdi.RampWearCostCoeff(:,1), 0, ng, ng);
w = mdi.Delta_T * mdi.tstep(1).TransMat(j,1); % the probability of going from initial state to jth
Q = spdiags(w * baseMVA^2 * mdi.RampWearCostCoeff(:,1) / mdi.Delta_T^2, 0, ng, ng);
c = -w * baseMVA * mdi.RampWearCostCoeff(:,1) .* mdi.InitialPg;
vs = struct('name', {'Pg'}, 'idx', {{1,j,1}});
k0 = w * 0.5 * mdi.RampWearCostCoeff(:,1)' * mdi.InitialPg.^2;
vs = struct('name', {'Pg'}, 'idx', {{1,j,1}});
om.add_quad_cost('RampWear', {1,j,1}, Q, c, k0, vs);
end
% Then the remaining periods
for t = 2:nt
for j2 = 1:mdi.idx.nj(t)
for j1 = 1:mdi.idx.nj(t-1)
w = mdi.tstep(t).TransMat(j2,j1) * mdi.CostWeights(1, j1, t-1);
h = w * baseMVA^2 * mdi.RampWearCostCoeff(:,t);
w = mdi.Delta_T * mdi.tstep(t).TransMat(j2,j1) * mdi.CostWeights(1, j1, t-1);
h = w * baseMVA^2 * mdi.RampWearCostCoeff(:,t) / mdi.Delta_T^2;
i = (1:ng)';
j = ng+(1:ng)';
Q = sparse([i;j;i;j], [i;i;j;j], [h;-h;-h;h], 2*ng, 2*ng);
Expand All @@ -1750,11 +1749,11 @@
% that makes sense for nt+1; all other fields in mdi.tstep(nt+1) can be empty.
if ~mdi.OpenEnded
for j = 1:mdi.idx.nj(nt)
w = mdi.tstep(nt+1).TransMat(1, j) * mdi.CostWeights(1, j, nt);
Q = spdiags(w * baseMVA^2 * mdi.RampWearCostCoeff(:,nt+1), 0, ng, ng);
w = mdi.Delta_T * mdi.tstep(nt+1).TransMat(1, j) * mdi.CostWeights(1, j, nt);
Q = spdiags(w * baseMVA^2 * mdi.RampWearCostCoeff(:,nt+1) / mdi.Delta_T^2, 0, ng, ng);
c = -w * baseMVA * mdi.RampWearCostCoeff(:,nt+1) .* mdi.TerminalPg;
vs = struct('name', {'Pg'}, 'idx', {{nt,j,1}});
k0 = w * 0.5 * mdi.RampWearCostCoeff(:,nt+1)' * mdi.TerminalPg.^2;
vs = struct('name', {'Pg'}, 'idx', {{nt,j,1}});
om.add_quad_cost('RampWear', {nt+1,j,1}, Q, c, k0, vs);
end
end
Expand All @@ -1773,7 +1772,7 @@
for t = 1:nt
for j = 1:mdi.idx.nj(t)
for k = 1:mdi.idx.nc(t,j)+1
w = mdi.CostWeightsAdj(k,j,t); %% NOTE (k,j,t) order !!!
w = mdi.Delta_T * mdi.CostWeightsAdj(k,j,t); %% NOTE (k,j,t) order !!!

% weighted polynomial energy costs for committed units
gc = mdi.flow(t,j,k).mpc.gencost;
Expand Down Expand Up @@ -1841,21 +1840,21 @@
end

% contingency reserve costs
c = baseMVA * mdi.StepProb(t) * mdi.offer(t).PositiveActiveReservePrice(:);
c = mdi.Delta_T * baseMVA * mdi.StepProb(t) * mdi.offer(t).PositiveActiveReservePrice(:);
vs = struct('name', {'Rpp'}, 'idx', {{t}});
om.add_quad_cost('Crpp', {t}, [], c, 0, vs);
c = baseMVA * mdi.StepProb(t) * mdi.offer(t).NegativeActiveReservePrice(:);
c = mdi.Delta_T * baseMVA * mdi.StepProb(t) * mdi.offer(t).NegativeActiveReservePrice(:);
vs = struct('name', {'Rpm'}, 'idx', {{t}});
om.add_quad_cost('Crpm', {t}, [], c, 0, vs);
end
% Assign load following ramp reserve costs. Do first nt periods first
om.init_indexed_name('qdc', 'Crrp', {mdi.idx.ntramp});
om.init_indexed_name('qdc', 'Crrm', {mdi.idx.ntramp});
for t = 1:mdi.idx.ntramp
c = baseMVA * mdi.StepProb(t) * mdi.offer(t).PositiveLoadFollowReservePrice(:);
c = mdi.Delta_T * baseMVA * mdi.StepProb(t) * mdi.offer(t).PositiveLoadFollowReservePrice(:);
vs = struct('name', {'Rrp'}, 'idx', {{t}});
om.add_quad_cost('Crrp', {t}, [], c, 0, vs);
c = baseMVA * mdi.StepProb(t) * mdi.offer(t).NegativeLoadFollowReservePrice(:);
c = mdi.Delta_T * baseMVA * mdi.StepProb(t) * mdi.offer(t).NegativeLoadFollowReservePrice(:);
vs = struct('name', {'Rrm'}, 'idx', {{t}});
om.add_quad_cost('Crrm', {t}, [], c, 0, vs);
end
Expand Down
2 changes: 1 addition & 1 deletion lib/mostver.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
v = struct( 'Name', 'MOST', ...
'Version', '1.2+', ...
'Release', '', ...
'Date', '30-May-2023' );
'Date', '25-Oct-2023' );
if nargout > 0
if nargin > 0
rv = v;
Expand Down
6 changes: 5 additions & 1 deletion lib/t/t_most_uc.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function t_most_uc(quiet, create_plots, create_pdfs, savedir)
% fcn = {'gurobi'};
% solvers = {'MOSEK'};
% fcn = {'mosek'};
ntests = 68;
ntests = 69;
t_begin(ntests*length(solvers), quiet);

if quiet
Expand Down Expand Up @@ -235,6 +235,10 @@ function t_most_uc(quiet, create_plots, create_pdfs, savedir)
plot_case('+ DC Network', mdo, ms, 500, 100, savedir, pp, fname);
end
% keyboard;
mdi.Delta_T = 2;
mdo = most(mdi, mpopt);
ms = most_summary(mdo);
t_is(ms.f, 2 * ex.f, 8, [t '(Delta_T = 2) : f']);

t = sprintf('%s : + startup/shutdown costs : ', solvers{s});
if mpopt.out.all
Expand Down