Skip to content

Commit 2b7f664

Browse files
committed
Add 'fix_integer' option for mp.opt_model.solve().
Set to true to fix any integer variables to their initial values, as specified in x0 or opt.x0.
1 parent a78ea0f commit 2b7f664

7 files changed

Lines changed: 124 additions & 53 deletions

File tree

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ since version 5.0
66
-----------------
77

88
#### 10/21/25
9+
- Add `fix_integer` option for `mp.opt_model.solve()`. Set to true to fix
10+
any integer variables to their initial values, as specified in ``x0`` or
11+
``opt.x0``.
912
- Add MILP example from `examples/miqp_ex1.m` to MILP tests.
1013
- Fix typo bug in dimension checking in `set_params()` for quadratic costs.
1114

docs/src/MP-Opt-Model-manual/MP-Opt-Model-manual.tex

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@
151151
\newcommand{\mpomlink}[0]{\href{\mpomurl}{\mpom{}}}
152152
\newcommand{\mpomname}[0]{\mpom{}}
153153
% \newcommand{\mpomname}[0]{{{\bf M}{\sc at}{\bf P}{\sc ower} \textbf{Opt}imization \textbf{Model}}}
154-
\newcommand{\mpomver}[0]{5.0}
154+
\newcommand{\mpomver}[0]{5.1-dev}
155155
\newcommand{\most}[0]{{MOST}}
156156
\newcommand{\mostname}[0]{{{\bf M}{\sc atpower} \textbf{O}ptimal \textbf{S}cheduling \textbf{T}ool}}
157157
\newcommand{\mosturl}[0]{https://github.com/MATPOWER/most}
@@ -2712,6 +2712,7 @@ \subsection{Solving the Model}
27122712
\code{~~parse\_soln} & 0 & flag that specifies whether or not to call the \code{parse\_soln} method and place the return values in the \code{soln}
27132713
property of the field type objects \\
27142714
\code{~~relax\_integer} & 0 & relax integer constraints, if true \\
2715+
\code{~~fix\_integer} & 0 & fix integer variables at value in \code{x0}, if true \\
27152716
\code{~~x0} & \emph{empty} & optional initial value of $x$, overrides value stored in model, \emph{(ignored by some solvers)} \\
27162717
\midrule
27172718
\multicolumn{3}{l}{\emph{Additional Options for Specific Problem Types}} \\
@@ -5195,6 +5196,7 @@ \subsection{Solving the Model}
51955196
\code{~~parse\_soln} & 0 & flag that specifies whether or not to call the \code{parse\_soln} method and place the return values in the \code{soln}
51965197
property of the field type objects \\
51975198
\code{~~relax\_integer} & 0 & relax integer constraints, if true \\
5199+
\code{~~fix\_integer} & 0 & fix integer variables at value in \code{x0}, if true \\
51985200
\code{~~x0} & \emph{empty} & optional initial value of $x$, overrides value stored in model, \emph{(ignored by some solvers)} \\
51995201
\midrule
52005202
\multicolumn{3}{l}{\emph{Additional Options for Specific Problem Types}} \\
@@ -6708,10 +6710,37 @@ \subsubsection*{Incompatible Changes}
67086710
\item Remove support for older versions of \knitro{}, including all references to \code{ktrlink} for pre-v9 versions. Currently supports \knitro{} version 13.1 and later.
67096711
\end{itemize}
67106712

6711-
% \subsection{Version 5.0 -- released ??? ?, 202?}
6712-
% \label{app:v50}
6713+
\subsection{Version 5.1 -- released ??? ?, 202?}
6714+
\label{app:v51}
6715+
6716+
\hl{\emph{(not yet released)}}
6717+
The \href{https://matpower.org/docs/MP-Opt-Model-manual-5.1.pdf}{\mpom{} 5.1 User's Manual} is available online.\footnote{\url{https://matpower.org/docs/MP-Opt-Model-manual-5.1.pdf}}
6718+
6719+
\subsubsection*{New Features}
6720+
\begin{itemize}
6721+
\item New \code{fix\_integer} option for \code{mp.opt\_model.solve()}. Set to true to fix any integer variables to their initial values, as specified in \code{x0} or \code{opt.x0}.
6722+
\item New functions:
6723+
\begin{itemize}
6724+
\item \code{foobar()} does whizbang.
6725+
\end{itemize}
6726+
\end{itemize}
6727+
6728+
\subsubsection*{Bugs Fixed}
6729+
\begin{itemize}
6730+
\item
6731+
\emph{Thanks to Fulano.}
6732+
\end{itemize}
6733+
6734+
\subsubsection*{Other Changes}
6735+
\begin{itemize}
6736+
\item
6737+
6738+
\end{itemize}
6739+
6740+
% \subsection{Version 6.0 -- released ??? ?, 202?}
6741+
% \label{app:v60}
67136742
%
6714-
% The \href{https://matpower.org/docs/MP-Opt-Model-manual-5.0.pdf}{\mpom{} 5.0 User's Manual} is available online.\footnote{\url{https://matpower.org/docs/MP-Opt-Model-manual-5.0.pdf}}
6743+
% The \href{https://matpower.org/docs/MP-Opt-Model-manual-6.0.pdf}{\mpom{} 6.0 User's Manual} is available online.\footnote{\url{https://matpower.org/docs/MP-Opt-Model-manual-6.0.pdf}}
67156744
%
67166745
% \subsubsection*{New Features}
67176746
% \begin{itemize}

lib/+mp/opt_model.m

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,8 @@
667667
% required to avoid mismatch warning message if mixed
668668
% integer price computation stage is not skipped
669669
% - ``relax_integer`` (0) - relax integer constraints, if true
670+
% - ``fix_integer`` (0) - fix integer variables at value in
671+
% ``x0``, if true
670672
% - ``skip_prices`` (0) - flag that specifies whether or not
671673
% to skip the price computation stage for mixed integer
672674
% problems, in which the problem is re-solved for only the
@@ -773,17 +775,11 @@
773775
[x, f, eflag, output, lambda] = pnes_master(fcn, x0, opt);
774776
end
775777
case {'MINLP', 'NLP'}
776-
mixed_integer = strcmp(pt(1:2), 'MI') && ...
777-
(~isfield(opt, 'relax_integer') || ~opt.relax_integer);
778+
[mixed_integer, x0, xmin, xmax, vtype] = ...
779+
mm.mixed_integer_helper(opt);
778780
if mixed_integer %% MINLP - mixed integer non-linear program
779781
error('mp.opt_model.solve: not yet implemented for ''MINLP'' problems.')
780782
else %% NLP - nonlinear program
781-
%% optimization vars, bounds, types
782-
[x0, xmin, xmax] = mm.var.params();
783-
if isfield(opt, 'x0')
784-
x0 = opt.x0;
785-
end
786-
787783
%% run solver
788784
[A, l, u] = mm.lin.params(mm.var);
789785
f_fcn = @(x)nlp_costfcn(mm, x);
@@ -797,16 +793,9 @@
797793
[HH, CC, C0] = mm.qdc.params(mm.var);
798794
[Q, B, ll, uu] = mm.qcn.params(mm.var);
799795
[A, l, u] = mm.lin.params(mm.var);
800-
mixed_integer = strcmp(pt(1:2), 'MI') && ...
801-
(~isfield(opt, 'relax_integer') || ~opt.relax_integer);
802-
796+
[mixed_integer, x0, xmin, xmax, vtype] = ...
797+
mm.mixed_integer_helper(opt);
803798
if mixed_integer
804-
%% optimization vars, bounds, types
805-
[x0, xmin, xmax, vtype] = mm.var.params();
806-
if isfield(opt, 'x0')
807-
x0 = opt.x0;
808-
end
809-
810799
%% run solver
811800
if isempty(Q) %% MILP, MIQP - mixed integer linear/quadratic program
812801
[x, f, eflag, output, lambda] = ...
@@ -817,12 +806,6 @@
817806
% miqcqps_master(HH, CC, Q, B, k, ll, uu, A, l, u, xmin, xmax, x0, vtype, opt);
818807
end
819808
else %% LP, QP - linear/quadratic program
820-
%% optimization vars, bounds, types
821-
[x0, xmin, xmax] = mm.var.params();
822-
if isfield(opt, 'x0')
823-
x0 = opt.x0;
824-
end
825-
826809
%% run solver
827810
if isempty(Q) %% LP, QP - linear/quadratic program
828811
[x, f, eflag, output, lambda] = ...
@@ -1129,6 +1112,37 @@ function display(mm, varargin)
11291112
end
11301113
end
11311114
end %% methods
1115+
1116+
methods (Access=protected)
1117+
function [mixed_integer, x0, xmin, xmax, vtype] = mixed_integer_helper(mm, opt)
1118+
%
1119+
pt = mm.problem_type();
1120+
mixed_integer = strcmp(pt(1:2), 'MI') && ...
1121+
(~isfield(opt, 'relax_integer') || ~opt.relax_integer);
1122+
if mixed_integer
1123+
%% optimization vars, bounds, types
1124+
[x0, xmin, xmax, vtype] = mm.var.params();
1125+
if isfield(opt, 'x0')
1126+
x0 = opt.x0;
1127+
end
1128+
1129+
if isfield(opt, 'fix_integer') && opt.fix_integer
1130+
%% fix integer variables
1131+
j = find(vtype == 'B' | vtype == 'I')';
1132+
xmin(j) = x0(j);
1133+
xmax(j) = x0(j);
1134+
mixed_integer = false;
1135+
end
1136+
else
1137+
%% optimization vars, bounds, types
1138+
[x0, xmin, xmax] = mm.var.params();
1139+
if isfield(opt, 'x0')
1140+
x0 = opt.x0;
1141+
end
1142+
vtype = [];
1143+
end
1144+
end
1145+
end
11321146
end %% classdef
11331147

11341148
function d = copy_prop(s, d, prop)

lib/@opt_model/solve.m

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
% mis-match warning message if mixed integer price computation
9292
% stage is not skipped
9393
% relax_integer (0) - relax integer constraints, if true
94+
% fix_integer (0) - fix integer variables at value in x0, if true
9495
% skip_prices (0) - flag that specifies whether or not to skip the
9596
% price computation stage for mixed integer problems, in which
9697
% the problem is re-solved for only the continuous variables,
@@ -200,17 +201,10 @@
200201
[x, f, eflag, output, lambda] = pnes_master(fcn, x0, opt);
201202
end
202203
case {'MINLP', 'NLP'}
203-
mixed_integer = strcmp(pt(1:2), 'MI') && ...
204-
(~isfield(opt, 'relax_integer') || ~opt.relax_integer);
204+
[mixed_integer, x0, xmin, xmax, vtype] = mixed_integer_helper(om, opt);
205205
if mixed_integer %% MINLP - mixed integer non-linear program
206206
error('opt_model.solve: not yet implemented for ''MINLP'' problems.')
207207
else %% NLP - nonlinear program
208-
%% optimization vars, bounds, types
209-
[x0, xmin, xmax] = om.params_var();
210-
if isfield(opt, 'x0')
211-
x0 = opt.x0;
212-
end
213-
214208
%% run solver
215209
[A, l, u] = om.params_lin_constraint();
216210
f_fcn = @(x)nlp_costfcn(om, x);
@@ -224,16 +218,8 @@
224218
[HH, CC, C0] = om.params_quad_cost();
225219
[Q, B, ll, uu] = om.qcn.params(om.var);
226220
[A, l, u] = om.params_lin_constraint();
227-
mixed_integer = strcmp(pt(1:2), 'MI') && ...
228-
(~isfield(opt, 'relax_integer') || ~opt.relax_integer);
229-
221+
[mixed_integer, x0, xmin, xmax, vtype] = mixed_integer_helper(om, opt);
230222
if mixed_integer
231-
%% optimization vars, bounds, types
232-
[x0, xmin, xmax, vtype] = om.params_var();
233-
if isfield(opt, 'x0')
234-
x0 = opt.x0;
235-
end
236-
237223
%% run solver
238224
if isempty(Q) %% MILP, MIQP - mixed integer linear/quadratic program
239225
[x, f, eflag, output, lambda] = ...
@@ -244,12 +230,6 @@
244230
% miqcqps_master(HH, CC, Q, B, k, ll, uu, A, l, u, xmin, xmax, x0, vtype, opt);
245231
end
246232
else %% LP, QP - linear/quadratic program
247-
%% optimization vars, bounds, types
248-
[x0, xmin, xmax] = om.params_var();
249-
if isfield(opt, 'x0')
250-
x0 = opt.x0;
251-
end
252-
253233
%% run solver
254234
if isempty(Q) %% LP, QP - linear/quadratic program
255235
[x, f, eflag, output, lambda] = ...
@@ -304,3 +284,30 @@
304284
end
305285
end
306286
f = [fnln; fqcn; flin];
287+
288+
function [mixed_integer, x0, xmin, xmax, vtype] = mixed_integer_helper(om, opt)
289+
pt = om.problem_type();
290+
mixed_integer = strcmp(pt(1:2), 'MI') && ...
291+
(~isfield(opt, 'relax_integer') || ~opt.relax_integer);
292+
if mixed_integer
293+
%% optimization vars, bounds, types
294+
[x0, xmin, xmax, vtype] = om.var.params();
295+
if isfield(opt, 'x0')
296+
x0 = opt.x0;
297+
end
298+
299+
if isfield(opt, 'fix_integer') && opt.fix_integer
300+
%% fix integer variables
301+
j = find(vtype == 'B' | vtype == 'I')';
302+
xmin(j) = x0(j);
303+
xmax(j) = x0(j);
304+
mixed_integer = false;
305+
end
306+
else
307+
%% optimization vars, bounds, types
308+
[x0, xmin, xmax] = om.var.params();
309+
if isfield(opt, 'x0')
310+
x0 = opt.x0;
311+
end
312+
vtype = [];
313+
end

lib/mpomver.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
% See https://github.com/MATPOWER/mp-opt-model for more info.
2525

2626
v = struct( 'Name', 'MP-Opt-Model', ...
27-
'Version', '5.0.1-dev', ...
27+
'Version', '5.1-dev', ...
2828
'Release', '', ...
2929
'Date', '21-Oct-2025' );
3030
if nargout > 0

lib/t/t_mm_solve_miqps.m

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function t_mm_solve_miqps(quiet)
2626
does_miqp(1) = 1;
2727
end
2828

29-
n = 51;
29+
n = 60;
3030
nmiqp = 20;
3131
nmiqp_soln = 30;
3232
diff_tool = 'bbdiff';
@@ -101,6 +101,8 @@ function t_mm_solve_miqps(quiet)
101101
end
102102
opt_r = opt;
103103
opt_r.relax_integer = 1;
104+
opt_f = opt;
105+
opt_f.fix_integer = 1;
104106

105107
% opt.verbose = 3;
106108
t = sprintf('%s - 2-d ILP : ', names{k});
@@ -216,6 +218,13 @@ function t_mm_solve_miqps(quiet)
216218
t_is(s, 1, 12, [t 'exitflag']);
217219
t_is(x, ex(:, 4), 12, [t 'x']);
218220
t_is(f, ef(Scenario, 2), 12, [t 'f']);
221+
222+
t = sprintf('%s - 14-d MILP Scenario %d (integer fixed) : ', names{k}, Scenario);
223+
opt_f.x0 = ones(size(x));
224+
[x, f, s, out, lam] = mm.solve(opt_f);
225+
t_is(s, 1, 12, [t 'exitflag']);
226+
t_is(x, ex(:, 3), 12, [t 'x']);
227+
t_is(f, ef(Scenario, 3), 12, [t 'f']);
219228
end
220229

221230
if does_miqp(k)

lib/t/t_om_solve_miqps.m

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function t_om_solve_miqps(quiet)
2626
does_miqp(1) = 1;
2727
end
2828

29-
n = 51;
29+
n = 60;
3030
nmiqp = 20;
3131
nmiqp_soln = 30;
3232
diff_tool = 'bbdiff';
@@ -101,6 +101,8 @@ function t_om_solve_miqps(quiet)
101101
end
102102
opt_r = opt;
103103
opt_r.relax_integer = 1;
104+
opt_f = opt;
105+
opt_f.fix_integer = 1;
104106

105107
% opt.verbose = 3;
106108
t = sprintf('%s - 2-d ILP : ', names{k});
@@ -216,6 +218,13 @@ function t_om_solve_miqps(quiet)
216218
t_is(s, 1, 12, [t 'exitflag']);
217219
t_is(x, ex(:, 4), 12, [t 'x']);
218220
t_is(f, ef(Scenario, 2), 12, [t 'f']);
221+
222+
t = sprintf('%s - 14-d MILP Scenario %d (integer fixed) : ', names{k}, Scenario);
223+
opt_f.x0 = ones(size(x));
224+
[x, f, s, out, lam] = om.solve(opt_f);
225+
t_is(s, 1, 12, [t 'exitflag']);
226+
t_is(x, ex(:, 3), 12, [t 'x']);
227+
t_is(f, ef(Scenario, 3), 12, [t 'f']);
219228
end
220229

221230
if does_miqp(k)

0 commit comments

Comments
 (0)