LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
LineOptSolver.m
1classdef LineOptSolver < handle
2 % LineOptSolver Main line-opt solver. Minimizes a penalized scalar
3 % objective (constraints as penalties) over the decision variables,
4 % aggregating across scenarios, using the self-contained
5 % opt.de.DifferentialEvolution engine (numpy-exact RNG) or an analytic/FD
6 % projected-gradient path. Mirrors native-Python LineOptSolver.
7
8 properties
9 problem
10 opt
11 fixedValueMap
12 evaluators % cell of opt.LineEvaluator
13 scenarioWeights
14 caches % cell of containers.Map
15 iterations = 0;
16 bestValue = inf;
17 bestX = [];
18 convergenceHistory = [];
19 startTime
20 deadline = inf;
21 end
22
23 methods
24 function obj = LineOptSolver(problem, options)
25 obj.problem = problem;
26 obj.opt = options;
27 obj.fixedValueMap = containers.Map('KeyType', 'char', 'ValueType', 'any');
28 fixed = problem.getFixedVariables();
29 for k = 1:numel(fixed)
30 obj.fixedValueMap(fixed{k}{1}.getName()) = fixed{k}{2};
31 end
32 obj.evaluators = {opt.LineEvaluator(problem.getModel(), problem.getVariables(), fixed)};
33 scen = problem.getScenarios();
34 w = zeros(1, 1 + numel(scen));
35 w(1) = 1.0;
36 for i = 1:numel(scen)
37 obj.evaluators{end+1} = opt.LineEvaluator(scen{i}{1}, problem.getVariables(), fixed);
38 w(i+1) = scen{i}{2};
39 end
40 obj.scenarioWeights = w;
41 end
42
43 function result = solve(obj)
44 obj.startTime = tic;
45 obj.iterations = 0;
46 obj.bestValue = inf;
47 obj.bestX = [];
48 obj.convergenceHistory = [];
49 obj.caches = cell(1, numel(obj.evaluators));
50 for i = 1:numel(obj.evaluators)
51 obj.caches{i} = containers.Map('KeyType', 'char', 'ValueType', 'any');
52 end
53 obj.deadline = obj.opt.timeLimit;
54
55 bounds = obj.evaluators{1}.getBounds();
56 if isempty(bounds)
57 result = obj.buildEmptyResult();
58 return;
59 end
60 if obj.shouldUseGradient()
61 result = obj.solveGradient(bounds);
62 else
63 result = obj.solveEvolution(bounds);
64 end
65 end
66
67 function result = solveEvolution(obj, bounds)
68 low = bounds(:, 1).';
69 high = bounds(:, 2).';
70 if isempty(obj.opt.seed)
71 seed = mod(int64(feature('timing','cpucount')), 2^31);
72 else
73 seed = obj.opt.seed;
74 end
75 de = opt.de.DifferentialEvolution(@(x) obj.objectiveFunction(x), low, high, ...
76 obj.opt.strategy, obj.opt.popsize, obj.opt.maxIterations, ...
77 obj.opt.mutationLow, obj.opt.mutationHigh, obj.opt.recombination, obj.opt.tol, seed);
78 de.callback = @(bx, nit) obj.deCallback(nit);
79
80 timedOut = false;
81 try
82 r = de.solve();
83 resX = obj.engineBestVector(r);
84 resFun = r.fun;
85 catch ME
86 if strcmp(ME.identifier, 'LineOpt:TimeLimit')
87 timedOut = true;
88 if isempty(obj.bestX)
89 result = obj.buildEmptyResult(); return;
90 end
91 resX = obj.bestX; resFun = obj.bestValue;
92 else
93 rethrow(ME);
94 end
95 end
96 solveTime = toc(obj.startTime);
97 result = obj.buildResult(resX, resFun, solveTime);
98 if timedOut, result.terminatedBy = 'time_limit'; end
99 end
100
101 function stop = deCallback(obj, nit)
102 obj.iterations = nit;
103 obj.convergenceHistory(end+1) = obj.bestValue;
104 stop = toc(obj.startTime) >= obj.deadline;
105 end
106
107 function x = engineBestVector(obj, r)
108 if ~isempty(obj.bestX) && obj.bestValue <= r.fun
109 x = obj.bestX;
110 else
111 x = r.x;
112 end
113 end
114
115 function total = objectiveFunction(obj, x)
116 if toc(obj.startTime) >= obj.deadline
117 error('LineOpt:TimeLimit', 'time limit reached');
118 end
119 values = obj.evaluators{1}.decodeVariables(x);
120 allValues = obj.mergeValues(values);
121 objective = obj.problem.getObjective();
122 pw = obj.opt.penaltyWeight;
123 cons = obj.problem.getConstraints();
124 scenarioValues = zeros(1, numel(obj.evaluators));
125 for i = 1:numel(obj.evaluators)
126 res = obj.evaluators{i}.evaluateValuesWithCache(values, obj.caches{i});
127 if ~res.feasible
128 total = inf; return;
129 end
130 value = objective.evaluateWithPenalty(res, allValues, pw);
131 for c = 1:numel(cons)
132 value = value + cons{c}.evaluate(res, allValues) * pw;
133 end
134 scenarioValues(i) = value;
135 end
136 total = obj.aggregateScenarios(scenarioValues);
137 if total < obj.bestValue
138 obj.bestValue = total;
139 obj.bestX = x;
140 end
141 end
142
143 function av = mergeValues(obj, values)
144 % merge: start from fixed, overlay values
145 av = containers.Map('KeyType', 'char', 'ValueType', 'any');
146 fk = keys(obj.fixedValueMap);
147 for i = 1:numel(fk), av(fk{i}) = obj.fixedValueMap(fk{i}); end
148 vk = keys(values);
149 for i = 1:numel(vk), av(vk{i}) = values(vk{i}); end
150 end
151
152 function total = aggregateScenarios(obj, values)
153 if numel(values) == 1
154 total = values(1); return;
155 end
156 if strcmp(obj.opt.scenarioAggregation, 'mean')
157 total = sum(obj.scenarioWeights .* values) / sum(obj.scenarioWeights);
158 else
159 total = max(values);
160 end
161 end
162
163 function tf = shouldUseGradient(obj)
164 if strcmp(obj.opt.optimizer, 'gradient'), tf = true; return; end
165 if strcmp(obj.opt.optimizer, 'evolution'), tf = false; return; end
166 tf = obj.allContinuous();
167 end
168
169 function tf = allContinuous(obj)
170 vars = obj.problem.getVariables();
171 if isempty(vars), tf = false; return; end
172 tf = true;
173 for i = 1:numel(vars)
174 vt = vars{i}.getVariableType();
175 if ~any(strcmp(vt, {'service_rate', 'routing'}))
176 tf = false; return;
177 end
178 end
179 end
180
181 function result = solveGradient(obj, bounds)
182 dim = size(bounds, 1);
183 nStart = max(1, obj.opt.gradientRestarts);
184 if isempty(obj.opt.seed), sd = 0; else, sd = obj.opt.seed; end
185 rs = RandStream('mt19937ar', 'Seed', sd);
186 starts = {0.5 * ones(1, dim)};
187 for s = 2:nStart, starts{end+1} = rand(rs, 1, dim); end %#ok<AGROW>
188 best = []; bestFun = inf; timedOut = false;
189 for si = 1:numel(starts)
190 if toc(obj.startTime) >= obj.deadline, timedOut = true; break; end
191 try
192 xx = obj.projectedGradientDescent(starts{si});
193 fx = obj.objectiveFunction(xx);
194 if isfinite(fx) && fx < bestFun, bestFun = fx; best = xx; end
195 catch ME
196 if strcmp(ME.identifier, 'LineOpt:TimeLimit'), timedOut = true; break; else, rethrow(ME); end
197 end
198 end
199 if isempty(best) || (~isempty(obj.bestX) && obj.bestValue < bestFun)
200 if ~isempty(obj.bestX), best = obj.bestX; bestFun = obj.bestValue;
201 elseif isempty(best), result = obj.buildEmptyResult(); return; end
202 end
203 solveTime = toc(obj.startTime);
204 result = obj.buildResult(best, bestFun, solveTime);
205 if timedOut, result.terminatedBy = 'time_limit'; end
206 end
207
208 function x = projectedGradientDescent(obj, x0)
209 x = min(max(x0, 0), 1);
210 f = obj.objectiveFunction(x);
211 for iter = 1:obj.opt.maxIterations
212 g = obj.objectiveGradient(x);
213 if norm(g) < 1e-9, break; end
214 step = 1.0; improved = false;
215 for ls = 1:30
216 xn = min(max(x - step * g, 0), 1);
217 fn = obj.objectiveFunction(xn);
218 if isfinite(fn) && fn < f - 1e-12
219 x = xn; f = fn; improved = true; break;
220 end
221 step = step * 0.5;
222 end
223 if ~improved, break; end
224 end
225 end
226
227 function g = objectiveGradient(obj, x)
228 h = obj.opt.fdStep;
229 dim = numel(x);
230 g = zeros(1, dim);
231 f0 = [];
232 for i = 1:dim
233 xp = x; xm = x;
234 xp(i) = min(1.0, x(i) + h);
235 xm(i) = max(0.0, x(i) - h);
236 fp = obj.objectiveFunction(xp);
237 fm = obj.objectiveFunction(xm);
238 if isfinite(fp) && isfinite(fm) && xp(i) > xm(i)
239 g(i) = (fp - fm) / (xp(i) - xm(i)); continue;
240 end
241 if isempty(f0), f0 = obj.objectiveFunction(x); end
242 if isfinite(fp) && isfinite(f0) && xp(i) > x(i)
243 g(i) = (fp - f0) / (xp(i) - x(i));
244 elseif isfinite(fm) && isfinite(f0) && x(i) > xm(i)
245 g(i) = (f0 - fm) / (x(i) - xm(i));
246 else
247 g(i) = 0.0;
248 end
249 end
250 end
251
252 function result = buildResult(obj, x, objectiveValue, solveTime)
253 result = opt.OptimizationResult();
254 result.objectiveValue = objectiveValue;
255 vals = obj.evaluators{1}.decodeVariables(x);
256 vk = keys(vals);
257 for i = 1:numel(vk), result.variableValues(vk{i}) = vals(vk{i}); end
258 result.iterations = obj.iterations;
259 result.solveTime = solveTime;
260 evals = 0;
261 for i = 1:numel(obj.evaluators), evals = evals + obj.evaluators{i}.getEvaluationCount(); end
262 result.modelEvaluations = evals;
263 result.convergenceHistory = obj.convergenceHistory;
264
265 allValues = obj.mergeValues(result.variableValues);
266 objective = obj.problem.getObjective();
267 allCons = [objective.getConstraints(), obj.problem.getConstraints()];
268 result.feasible = true;
269 for i = 1:numel(obj.evaluators)
270 er = obj.evaluators{i}.evaluateValuesWithCache(result.variableValues, obj.caches{i});
271 if ~er.feasible, result.feasible = false; continue; end
272 for c = 1:numel(allCons)
273 viol = allCons{c}.evaluate(er, allValues);
274 if viol > 0
275 nm = allCons{c}.getName();
276 prev = 0.0; if isKey(result.constraintViolations, nm), prev = result.constraintViolations(nm); end
277 result.constraintViolations(nm) = max(viol, prev);
278 result.feasible = false;
279 end
280 end
281 end
282 elapsed = toc(obj.startTime);
283 if elapsed >= obj.opt.timeLimit
284 result.terminatedBy = 'time_limit';
285 elseif obj.iterations >= obj.opt.maxIterations
286 result.terminatedBy = 'iterations';
287 else
288 result.terminatedBy = 'convergence';
289 end
290 end
291
292 function result = buildEmptyResult(obj) %#ok<MANU>
293 result = opt.OptimizationResult();
294 result.objectiveValue = 0.0;
295 result.feasible = true;
296 result.terminatedBy = 'empty';
297 end
298 end
299end
Definition Station.m:245