Objective and Constraints Having a Common Function in Serial or Parallel, Problem-Based
This example shows how to avoid calling a function twice when it computes values for both the objective and the constraints using the problem-based approach. For the solver-based approach, see Objective and Nonlinear Constraints in the Same Function.
You typically use such a function in a simulation. Solvers usually evaluate the objective and nonlinear constraint functions separately. This evaluation is wasteful when you use the same calculation for both results.
This example also shows the effect of parallel computation on solver speed. For time-consuming functions, computing in parallel can speed the solver, as can avoiding calling the time-consuming function repeatedly at the same point. Using both techniques together speeds the solver the most.
Create Time-Consuming Function That Computes Several Quantities
The computeall
function returns outputs that are part of the objective and nonlinear constraints.
type computeall
function [f1,c1] = computeall(x) c1 = norm(x)^2 - 1; f1 = 100*(x(2) - x(1)^2)^2 + (1 - x(1))^2 + besselj(1,x(1)); pause(1) % simulate expensive computation end
The function includes a pause(1)
statement to simulate a time-consuming function.
Create Optimization Variables
This problem uses a four-element optimization variable.
x = optimvar('x',4);
Convert Function Using ReuseEvaluation
Convert the computeall
function to an optimization expression. To save time during the optimization, use the ReuseEvaluation
name-value argument. To save time for the solver to determine the output expression sizes (this happens only once), set the OutputSize
name-value argument to [1 1]
, indicating that both f
and c
are scalar.
[f,c] = fcn2optimexpr(@computeall,x,'ReuseEvaluation',true,'OutputSize',[1 1]);
Create Objective, Constraint, and Problem
Create the objective function from the f
expression.
obj = f + 20*(x(3) - x(4)^2)^2 + 5*(1 - x(4))^2;
Create the nonlinear inequality constraint from the c
expression.
cons = c <= 0;
Create an optimization problem and include the objective and constraint.
prob = optimproblem('Objective',obj);
prob.Constraints.cons = cons;
show(prob)
OptimizationProblem : Solve for: x minimize : ((arg3 + (20 .* (x(3) - x(4).^2).^2)) + (5 .* (1 - x(4)).^2)) where: [arg3,~] = computeall(x); subject to cons: arg_LHS <= 0 where: [~,arg_LHS] = computeall(x);
Solve Problem
Monitor the time it takes to solve the problem starting from the initial point x0.x = [-1;1;1;2]
.
x0.x = [-1;1;1;2];
x0.x = x0.x/norm(x0.x); % Feasible initial point
tic
[sol,fval,exitflag,output] = solve(prob,x0)
Solving problem using fmincon. Local minimum found that satisfies the constraints. Optimization completed because the objective function is non-decreasing in feasible directions, to within the value of the optimality tolerance, and constraints are satisfied to within the value of the constraint tolerance. <stopping criteria details>
sol = struct with fields:
x: [4×1 double]
fval = 0.9091
exitflag = OptimalSolution
output = struct with fields:
iterations: 24
funcCount: 142
constrviolation: 0
stepsize: 2.6813e-05
algorithm: 'interior-point'
firstorderopt: 1.0143e-06
cgiterations: 7
message: 'Local minimum found that satisfies the constraints.↵↵Optimization completed because the objective function is non-decreasing in ↵feasible directions, to within the value of the optimality tolerance,↵and constraints are satisfied to within the value of the constraint tolerance.↵↵<stopping criteria details>↵↵Optimization completed: The relative first-order optimality measure, 8.264724e-07,↵is less than options.OptimalityTolerance = 1.000000e-06, and the relative maximum constraint↵violation, 0.000000e+00, is less than options.ConstraintTolerance = 1.000000e-06.'
bestfeasible: [1×1 struct]
objectivederivative: "finite-differences"
constraintderivative: "finite-differences"
solver: 'fmincon'
time1 = toc
time1 = 144.0930
The number of seconds for the solution is just over the number of function evaluations, which indicates that the solver computed each evaluation only once.
fprintf("The number of seconds to solve was %g, and the number of evaluation points was %g.\n",time1,output.funcCount)
The number of seconds to solve was 144.093, and the number of evaluation points was 142.
If, instead, you do not call fcn2optimexpr
using ReuseEvaluation
, then the solution time doubles.
[f2,c2] = fcn2optimexpr(@computeall,x,'ReuseEvaluation',false,'Analysis','off'); obj2 = f2 + 20*(x(3) - x(4)^2)^2 + 5*(1 - x(4))^2; cons2 = c2 <= 0; prob2 = optimproblem('Objective',obj2); prob2.Constraints.cons2 = cons2; tic [sol2,fval2,exitflag2,output2] = solve(prob2,x0);
Solving problem using fmincon. Local minimum found that satisfies the constraints. Optimization completed because the objective function is non-decreasing in feasible directions, to within the value of the optimality tolerance, and constraints are satisfied to within the value of the constraint tolerance. <stopping criteria details>
time2 = toc
time2 = 286.3669
Parallel Processing
If you have a Parallel Computing Toolbox™ license, you can save even more time by computing in parallel. To do so, set options to use parallel processing, and call solve
with options.
options = optimoptions(prob,'UseParallel',true); tic [sol3,fval3,exitflag3,output3] = solve(prob,x0,'Options',options);
Solving problem using fmincon. Local minimum found that satisfies the constraints. Optimization completed because the objective function is non-decreasing in feasible directions, to within the value of the optimality tolerance, and constraints are satisfied to within the value of the constraint tolerance. <stopping criteria details>
time3 = toc
time3 = 72.1202
Using parallel processing and ReuseEvaluation
together provides a faster solution than using ReuseEvaluation
alone. See how long it takes to solve the problem using parallel processing alone.
tic
[sol4,fval4,exitflag4,output4] = solve(prob2,x0,'Options',options);
Solving problem using fmincon. Local minimum found that satisfies the constraints. Optimization completed because the objective function is non-decreasing in feasible directions, to within the value of the optimality tolerance, and constraints are satisfied to within the value of the constraint tolerance. <stopping criteria details>
time4 = toc
time4 = 136.8033
Summary of Timing Results
Combine the timing results into one table.
timingtable = table([time1;time2;time3;time4],... 'RowNames',["Reuse Serial";"No Reuse Serial";"Reuse Parallel";"No Reuse Parallel"])
timingtable=4×1 table
Var1
______
Reuse Serial 144.09
No Reuse Serial 286.37
Reuse Parallel 72.12
No Reuse Parallel 136.8
For this problem, on a computer with a 6-core processor, computing in parallel takes about half the time of computing in serial, and computing with ReuseEvaluation
takes about half the time of computing without ReuseEvaluation
. Computing in parallel with ReuseEvaluation
takes about a quarter of the time of computing in serial without ReuseEvaluation
.