1classdef BisectionSolver < handle
2 % BisectionSolver Exact O(log n) solver
for a single integer decision
3 % variable with monotone feasibility. direction=
'min_feasible' finds
the
4 % smallest feasible value (server sizing);
'max_feasible' the largest
5 % (population sizing). Mirrors native-Python
6 % line_solver.opt.sizing.BisectionSolver.
10 direction =
'min_feasible';
22 function obj = BisectionSolver(problem, direction)
23 obj.problem = problem;
24 if nargin >= 2 && ~isempty(direction), obj.direction = direction; end
25 vars = problem.getVariables();
26 if numel(vars) ~= 1 || vars{1}.getDimension() ~= 1
27 line_error(mfilename,
'BisectionSolver requires exactly one dimension-1 decision variable');
29 obj.variable = vars{1};
30 loV = obj.variable.decode(0.0);
31 hiV = obj.variable.decode(1.0);
32 if mod(loV,1) ~= 0 || mod(hiV,1) ~= 0
33 line_error(mfilename,
'BisectionSolver requires an integer-valued variable');
35 obj.lo = round(loV); obj.hi = round(hiV);
37 obj.fixedValueDict = containers.Map(
'KeyType',
'char',
'ValueType',
'any');
38 fixed = problem.getFixedVariables();
39 for k = 1:numel(fixed), obj.fixedValueDict(fixed{k}{1}.getName()) = fixed{k}{2}; end
40 obj.evaluators = {opt.LineEvaluator(problem.getModel(), vars, fixed)};
41 scen = problem.getScenarios();
43 obj.evaluators{end+1} = opt.LineEvaluator(scen{i}{1}, vars, fixed); %#ok<AGROW>
45 obj.violationsAt = containers.Map(
'KeyType',
'char',
'ValueType',
'double');
46 obj.probeCache = containers.Map(
'KeyType',
'double',
'ValueType',
'any');
49 function cons = allConstraints(obj)
50 objective = obj.problem.getObjective();
52 if ~isempty(objective), cons = objective.getConstraints(); end
53 cons = [cons, obj.problem.getConstraints()];
56 function feasible = probe(obj, value)
57 if isKey(obj.probeCache, value)
58 c = obj.probeCache(value);
59 obj.baseResultAt = c{2}; obj.violationsAt = c{3};
60 feasible = c{1};
return;
62 values = containers.Map(
'KeyType',
'char',
'ValueType',
'any');
63 values(obj.variable.getName()) = value;
64 allValues = containers.Map(
'KeyType',
'char',
'ValueType',
'any');
65 fk = keys(obj.fixedValueDict);
66 for i = 1:numel(fk), allValues(fk{i}) = obj.fixedValueDict(fk{i}); end
67 allValues(obj.variable.getName()) = value;
68 cons = obj.allConstraints();
71 violations = containers.Map(
'KeyType',
'char',
'ValueType',
'double');
73 for e = 1:numel(obj.evaluators)
74 res = obj.evaluators{e}.evaluateValues(values);
75 if isempty(baseResult), baseResult = res; end
76 if ~res.feasible, feasible =
false;
continue; end
78 v = cons{c}.evaluate(res, allValues);
81 nm = cons{c}.getName();
82 prev = 0.0;
if isKey(violations, nm), prev = violations(nm); end
83 violations(nm) = max(v, prev);
87 obj.baseResultAt = baseResult;
88 obj.violationsAt = violations;
89 obj.probeCache(value) = {feasible, baseResult, violations};
92 function result = solve(obj)
94 loV = obj.lo; hiV = obj.hi; iterations = 0;
95 if strcmp(obj.direction,
'min_feasible')
97 mid = floor((loV + hiV) / 2);
98 feasible = obj.probe(mid); iterations = iterations + 1;
99 if feasible, hiV = mid; else, loV = mid + 1; end
101 elseif strcmp(obj.direction, 'max_feasible')
103 mid = floor((loV + hiV + 1) / 2);
104 feasible = obj.probe(mid); iterations = iterations + 1;
105 if feasible, loV = mid; else, hiV = mid - 1; end
108 line_error(mfilename, ['Unknown direction: ' obj.direction]);
112 feasible = obj.probe(chosen);
114 result = opt.OptimizationResult();
115 result.variableValues(obj.variable.getName()) = chosen;
116 result.feasible = feasible;
117 vk = keys(obj.violationsAt);
118 for i = 1:numel(vk), result.constraintViolations(vk{i}) = obj.violationsAt(vk{i}); end
119 result.iterations = iterations;
121 for e = 1:numel(obj.evaluators), evals = evals + obj.evaluators{e}.getEvaluationCount(); end
122 result.modelEvaluations = evals;
123 result.terminatedBy =
'bisection';
125 objective = obj.problem.getObjective();
126 if ~isempty(objective) && ~isempty(obj.baseResultAt)
127 allValues = containers.Map('KeyType','
char','ValueType','any');
128 fk = keys(obj.fixedValueDict);
129 for i = 1:numel(fk), allValues(fk{i}) = obj.fixedValueDict(fk{i}); end
130 allValues(obj.variable.getName()) = chosen;
131 result.objectiveValue = objective.evaluate(obj.baseResultAt, allValues);
133 result.solveTime = toc(t0);