This example shows how to avoid calling a function twice when it computes values for both objective and constraints.
You typically use such a function in a simulation. Solvers such
as fmincon
evaluate the objective and nonlinear
constraint functions separately. This evaluation is wasteful when
you use the same calculation for both results.
To avoid wasting time, have your calculation use a nested function to evaluate the objective and constraint functions only when needed, by retaining the values of time-consuming calculations. Using a nested function avoids using global variables, yet lets intermediate results be retained and shared between the objective and constraint functions.
For example, suppose computeall
is the expensive
(time-consuming) function called by both the objective function and
by the nonlinear constraint functions. Suppose you want to use fmincon
as
your optimizer.
Write a function that computes a portion of Rosenbrock's
function f1
and a nonlinear constraint c1
that
keeps the solution in a disk of radius 1 around the origin. Rosenbrock's
function is
which has a unique minimum value of 0 at (1,1). See Solve a Constrained Nonlinear Problem.
In this example there is no nonlinear equality constraint,
so ceq1 = []
.
Add a pause(1)
statement to simulate an expensive
computation.
function [f1,c1,ceq1] = computeall(x) ceq1 = []; c1 = norm(x)^2 - 1; f1 = 100*(x(2) - x(1)^2)^2 + (1-x(1))^2; pause(1) % simulate expensive computation end
Save computeall.m
as a file on your MATLAB® path.
Suppose the objective function is
y = 100(x2 – x12)2 +
(1 – x1)2
+ 20*(x3 – x42)2 +
5*(1 – x4)2.
computeall
returns the first part of the
objective function. Embed the call to computeall
in
a nested function:
function [x,f,eflag,outpt] = runobjconstr(x0,opts) if nargin == 1 % No options supplied opts = []; end xLast = []; % Last place computeall was called myf = []; % Use for objective at xLast myc = []; % Use for nonlinear inequality constraint myceq = []; % Use for nonlinear equality constraint fun = @objfun; % the objective function, nested below cfun = @constr; % the constraint function, nested below % Call fmincon [x,f,eflag,outpt] = fmincon(fun,x0,[],[],[],[],[],[],cfun,opts); function y = objfun(x) if ~isequal(x,xLast) % Check if computation is necessary [myf,myc,myceq] = computeall(x); xLast = x; end % Now compute objective function y = myf + 20*(x(3) - x(4)^2)^2 + 5*(1 - x(4))^2; end function [c,ceq] = constr(x) if ~isequal(x,xLast) % Check if computation is necessary [myf,myc,myceq] = computeall(x); xLast = x; end % Now compute constraint functions c = myc; % In this case, the computation is trivial ceq = myceq; end end
Save the nested function as a file named runobjconstr.m
on
your MATLAB path.
Run the file, timing the call with tic
and toc
.
opts = optimoptions(@fmincon,'Algorithm','interior-point','Display','off'); x0 = [-1,1,1,2]; tic [x,fval,exitflag,output] = runobjconstr(x0,opts); toc
Elapsed time is 203.797275 seconds.
Compare the times to run the solver with and without the nested
function. For the run without the nested function, save myrosen2.m
as
the objective function file, and constr.m
as the
constraint:
function y = myrosen2(x) f1 = computeall(x); % get first part of objective y = f1 + 20*(x(3) - x(4)^2)^2 + 5*(1 - x(4))^2; end function [c,ceq] = constr(x) [~,c,ceq] = computeall(x); end
Run fmincon
, timing the call with tic
and toc
.
tic
[x,fval,exitflag,output] = fmincon(@myrosen2,x0,...
[],[],[],[],[],[],@constr,opts);
toc
Elapsed time is 406.771978 seconds.
The solver takes twice as long as before, because it evaluates the objective and constraint separately.
If you have a Parallel Computing Toolbox™ license, you can
save even more time by setting the UseParallel
option
to true
.
parpool
Starting parallel pool (parpool) using the 'local' profile ... connected to 4 workers. ans = Pool with properties: Connected: true NumWorkers: 4 Cluster: local AttachedFiles: {} IdleTimeout: 30 minute(s) (30 minutes remaining) SpmdEnabled: true
opts = optimoptions(opts,'UseParallel',true);
tic
[x,fval,exitflag,output] = runobjconstr(x0,opts);
toc
Elapsed time is 97.528110 seconds.
In this case, enabling parallel computing cuts the computational time in half.
Compare the runs with parallel computing, with and without a nested function:
tic
[x,fval,exitflag,output] = fmincon(@myrosen2,x0,...
[],[],[],[],[],[],@constr,opts);
toc
Elapsed time is 188.985178 seconds.
In this example, computing in parallel but not nested takes about the same time as computing nested but not parallel. Computing both nested and parallel takes half the time of using either alone.