From 7923b99811a94a5674a29266f723fb4be2c809e4 Mon Sep 17 00:00:00 2001 From: Ray Zimmerman Date: Wed, 8 Nov 2023 12:01:56 -0700 Subject: [PATCH] Speed up building UC (min up/down time) constraints. Improvement can be quite substantial on large problems. --- CHANGES.md | 4 + docs/src/MOST-manual/MOST-manual.tex | 7 +- lib/most.m | 134 ++++++++++++++++++--------- lib/mostver.m | 2 +- 4 files changed, 101 insertions(+), 46 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ff79c8b..f579572 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,10 @@ Change history for MOST since 1.2 --------- +#### 11/8/23 + - Speed up building unit commitment (min up/down time) constraints. + Improvement can be quite substantial on large problems. + #### 11/6/23 - Reduce memory requirements for long horizon cases with storage by forming/storing transposes of matrices for storage constraints. diff --git a/docs/src/MOST-manual/MOST-manual.tex b/docs/src/MOST-manual/MOST-manual.tex index ec1f8b9..7d39ef0 100644 --- a/docs/src/MOST-manual/MOST-manual.tex +++ b/docs/src/MOST-manual/MOST-manual.tex @@ -159,7 +159,7 @@ \newcommand{\mostname}[0]{{{\bf M}{\sc atpower} \textbf{O}ptimal \textbf{S}cheduling \textbf{T}ool}} \newcommand{\mosturl}[0]{https://github.com/MATPOWER/most} \newcommand{\mostlink}[0]{\href{\mosturl}{\most{}}} -\newcommand{\mostver}[0]{1.2} +\newcommand{\mostver}[0]{1.2+} \newcommand{\md}[0]{{\most{} Data struct}} \newcommand{\powerweb}[0]{{\sc PowerWeb}} \newcommand{\pserc}[0]{{\sc PSerc}} @@ -240,7 +240,7 @@ %%% BEFORE PUBLISHING A NEW VERSION: %%% Update the publication year for \bibitem{matpower} and %%% \bibitem{matpower_manual} to the year of the latest release -\date{December 13, 2022} % comment this line to display the current date +%\date{December 13, 2022} % comment this line to display the current date %\date{December 14, 2011\thanks{Second revision. First revision was December 13, 2011}} % comment this line to display the current date %%% BEGIN DOCUMENT @@ -250,7 +250,7 @@ \vfill \begin{center} {\scriptsize -\copyright~2011--2022~\PSERC{}\\ +\copyright~2011--2023~\PSERC{}\\ All Rights Reserved} \end{center} @@ -3438,6 +3438,7 @@ \subsubsection*{Changes} \begin{itemize} \item Reduce memory requirements for long horizon cases with storage by forming/storing transposes of matrices for storage constraints.\\ \emph{Requires \mpomlink{} version~$>$~4.1.} +\item Speed up building unit commitment (min up/down time) constraints. Improvement can be quite substantial on large problems. \end{itemize} diff --git a/lib/most.m b/lib/most.m index 80af018..c974074 100644 --- a/lib/most.m +++ b/lib/most.m @@ -1574,59 +1574,109 @@ end om.add_lin_constraint('uvw', {t}, A, b, b, vs); end - % Then continue with minimimum up time constraints. Again, two - % different forms depending on whether the horizon is cyclical or not - om.init_indexed_name('lin', 'minup', {nt, ng}); + % Continue with minimimum up time constraints, with option for + % cyclical horizon. + om.init_indexed_name('lin', 'minup', {nt}); + % Sum of startups in "min up window" (win) must be <= u for t = 1:nt + % beginning of win + w0 = t - mdi.UC.MinUp + 1; % full win(t,i) is w0(i):t + if mdi.UC.CyclicCommitment % window is circular + % any window that starts before t=1 wraps back to end of horizon + w0(w0 < 1) = w0(w0 < 1) + nt; + % if we wrap all the way back to before t=1 again, + % include entire horizon, i.e. always ON + if t < nt + w0(w0 < 1) = t + 1; + else + w0(w0 < 1) = 1; + end + else + % clip non-positive w0 + w0(w0 < 1) = 1; + end + + % b = -(sum of startups in win before t=1) + b = zeros(ng, 1); + % next line not necessary, since it is handled above by direct bounds on u + % b(mdi.UC.InitialState > 0 & w0 <= -mdi.UC.InitialState +1) = -1; + + % period index list from t back to beginning of biggest win + if any(w0 > t) % w/wrapping back to end of horizon + max_win = [t:-1:1 nt:-1:min(w0(w0 > t))]; + else % no wrapping back to end of horizon + max_win = [t:-1:min(w0)]; + end + + vs = struct('name', {'u'}, 'idx', {{t}}); + A = [-speye(ng, ng) sparse(ng, ng*length(max_win))]; + for tt = 1:length(max_win) + vs(end+1).name = 'v'; + vs(end).idx = {max_win(tt)}; + end for i = 1:ng - ti = t-mdi.UC.MinUp(i)+1:t; - if mdi.UC.CyclicCommitment % window is circular - for tt = 1:length(ti) - if ti(tt) < 1 - ti(tt) = nt + ti(tt); - end - end + if w0(i) > t + win = [t:-1:1 nt:-1:w0(i)]; + else + win = [t:-1:w0(i)]; end - % limit to positive time - % even with CyclicCommitment, in case MinUp is longer than horizon - % (which implies always ON or always OFF) - ti = ti(ti>0); - vs = struct('name', {'u'}, 'idx', {{t}}); - A = sparse(1, i, -1, 1, ng); - for tt = 1:length(ti) - vs(end+1).name = 'v'; - vs(end).idx = {ti(tt)}; - A = [A sparse(1, i, 1, 1, ng)]; + for tt = 1:length(win) + A(i, ng*tt+i) = 1; end - om.add_lin_constraint('minup', {t, i}, A, [], 0, vs); end + om.add_lin_constraint('minup', {t}, A, [], b, vs); end - % Continue with minimimum downtime constraints. Two - % different forms depending on whether the horizon is cyclical or not - om.init_indexed_name('lin', 'mindown', {nt, ng}); + % Continue with minimimum downtime constraints, with option for + % cyclical horizon. + om.init_indexed_name('lin', 'mindown', {nt}); + % Sum of shutdowns in "min down window" (win) must be <= (1-u) for t = 1:nt + % beginning of win + w0 = t - mdi.UC.MinDown + 1; % full win(t,i) is w0(i):t + if mdi.UC.CyclicCommitment % window is circular + % any window that starts before t=1 wraps back to end of horizon + w0(w0 < 1) = w0(w0 < 1) + nt; + % if we wrap all the way back to before t=1 again, + % include entire horizon, i.e. always ON + if t < nt + w0(w0 < 1) = t + 1; + else + w0(w0 < 1) = 1; + end + else + % clip non-positive w0 + w0(w0 < 1) = 1; + end + + % b = 1-(sum of shutdowns in win before t=1) + b = ones(ng, 1); + % next line not necessary, since it is handled above by direct bounds on u + % b(mdi.UC.InitialState < 0 & w0 <= -mdi.UC.InitialState +1) = 0; + + % period index list from t back to beginning of biggest win + if any(w0 > t) % w/wrapping back to end of horizon + max_win = [t:-1:1 nt:-1:min(w0(w0 > t))]; + else % no wrapping back to end of horizon + max_win = [t:-1:min(w0)]; + end + + vs = struct('name', {'u'}, 'idx', {{t}}); + A = [speye(ng, ng) sparse(ng, ng*length(max_win))]; + for tt = 1:length(max_win) + vs(end+1).name = 'w'; + vs(end).idx = {max_win(tt)}; + end for i = 1:ng - ti = t-mdi.UC.MinDown(i)+1:t; - if mdi.UC.CyclicCommitment % window is circular - for tt = 1:length(ti) - if ti(tt) < 1 - ti(tt) = nt + ti(tt); - end - end + if w0(i) > t + win = [t:-1:1 nt:-1:w0(i)]; + else + win = [t:-1:w0(i)]; end - % limit to positive time - % even with CyclicCommitment, in case MinDown is longer than horizon - % (which implies always ON or always OFF) - ti = ti(ti>0); - vs = struct('name', {'u'}, 'idx', {{t}}); - A = sparse(1, i, 1, 1, ng); - for tt = 1:length(ti) - vs(end+1).name = 'w'; - vs(end).idx = {ti(tt)}; - A = [A sparse(1, i, 1, 1, ng)]; + for tt = 1:length(win) + A(i, ng*tt+i) = 1; end - om.add_lin_constraint('mindown', {t, i}, A, [], 1, vs); end + om.add_lin_constraint('mindown', {t}, A, [], b, vs); end % Limit generation ranges based on commitment status; first Pmax; % p - u*Pmax <= 0 diff --git a/lib/mostver.m b/lib/mostver.m index fba30dc..0105aa6 100644 --- a/lib/mostver.m +++ b/lib/mostver.m @@ -19,7 +19,7 @@ v = struct( 'Name', 'MOST', ... 'Version', '1.2+', ... 'Release', '', ... - 'Date', '06-Nov-2023' ); + 'Date', '08-Nov-2023' ); if nargout > 0 if nargin > 0 rv = v;