Objective and Nonlinear Constraints in the Same Function
This example shows how to avoid calling a function twice when it computes values for both the objective and constraints using the solver-based approach. To avoid calling a function twice using the problem-based approach, see Objective and Constraints Having a Common Function in Serial or Parallel, Problem-Based.
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, use a nested function to evaluate the objective and constraint functions only when needed, by retaining the values of time-consuming calculations. This approach avoids using global variables, while retaining intermediate results be retained and sharing them between the objective and constraint functions.
Note
Because of the way ga
(Global Optimization Toolbox) calls nonlinear constraint
functions, the technique in this example usually does not reduce the number
of calls to the objective or constraint functions.
Step 1. Write a function that computes the objective and constraints.
Step 2. Embed the function in a nested function that keeps recent values.
Step 4. Determine the time to run without the nested function.
Step 1. Write a function that computes the objective and constraints.
For example, suppose computeall
is the expensive
(time-consuming) function called by both the objective function and the
nonlinear constraint functions. Assume that you want to use
fmincon
as your optimizer.
Write a function that computes a portion of Rosenbrock’s function
f1
and includes 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 Constrained Nonlinear Problem Using Optimize Live Editor Task or Solver.
This example has no nonlinear equality constraint, so
ceq1 = []
. Add a pause(1)
statement to simulate an expensive computation.
function [f1,c1,ceq1] = computeall(x) ceq1 = []; c1 = x(1)^2 + x(2)^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.
Step 2. Embed the function in a nested function that keeps recent values.
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 function 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.
Step 3. Determine the time to run with the nested function.
Run the function, 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 259.364090 seconds.
Step 4. Determine the time to run without the nested function.
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 518.364770 seconds.
The solver takes twice as long as before, because it evaluates the objective and constraint separately.
Step 5. Save computing time with parallel computing.
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 the parallel pool (number of workers: 6). ans = ProcessPool with properties: Connected: true NumWorkers: 6 Cluster: local AttachedFiles: {} AutoAddClientPath: true IdleTimeout: 30 minutes (30 minutes remaining) SpmdEnabled: true
opts = optimoptions(opts,'UseParallel',true);
tic
[x,fval,exitflag,output] = runobjconstr(x0,opts);
toc
Elapsed time is 121.151203 seconds.
In this case, enabling parallel computing reduces the computational time in half, compared to the serial run with the nested function.
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 235.914597 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.