LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
DecompositionWorkflow.m
1classdef DecompositionWorkflow < handle
2 % DecompositionWorkflow Decomposes a joint problem into per-variable-type
3 % subproblems solved via Gauss-Seidel cycling with fixed-value propagation.
4 % An internal topological sort orders subproblems when dependencies are set.
5 % Mirrors native-Python DecompositionWorkflow.
6
7 properties (Constant)
8 DEFAULT_ORDER = {'server_allocation','station_replicas','service_rate', ...
9 'job_population','class_priority','routing','class_mapping'};
10 end
11
12 properties
13 problem
14 subproblems = {};
15 dependencyGraph % Map toNode -> cell of fromNodes
16 solverOptions
17 end
18
19 methods
20 function obj = DecompositionWorkflow(problem)
21 obj.problem = problem;
22 obj.dependencyGraph = containers.Map('KeyType','char','ValueType','any');
23 obj.solverOptions = opt.LineOptSolverOptions();
24 end
25
26 function p = getProblem(obj), p = obj.problem; end
27 function s = getSubProblems(obj), s = obj.subproblems; end
28 function obj = setSolverOptions(obj, options), obj.solverOptions = options; end
29
30 function obj = autoDecompose(obj)
31 byType = containers.Map('KeyType','char','ValueType','any');
32 vars = obj.problem.getVariables();
33 for i = 1:numel(vars)
34 t = vars{i}.getVariableType();
35 if isKey(byType, t), lst = byType(t); else, lst = {}; end
36 lst{end+1} = vars{i}; %#ok<AGROW>
37 byType(t) = lst;
38 end
39 obj.subproblems = {};
40 used = containers.Map('KeyType','char','ValueType','logical');
41 for k = 1:numel(obj.DEFAULT_ORDER)
42 t = obj.DEFAULT_ORDER{k};
43 if isKey(byType, t)
44 obj.subproblems{end+1} = opt.SubProblem(t, t, byType(t)); %#ok<AGROW>
45 used(t) = true;
46 end
47 end
48 bk = keys(byType);
49 for k = 1:numel(bk)
50 if ~isKey(used, bk{k})
51 obj.subproblems{end+1} = opt.SubProblem(bk{k}, bk{k}, byType(bk{k})); %#ok<AGROW>
52 end
53 end
54 end
55
56 function obj = setDependency(obj, fromProblem, toProblem)
57 if isKey(obj.dependencyGraph, toProblem), s = obj.dependencyGraph(toProblem); else, s = {}; end
58 s{end+1} = fromProblem;
59 obj.dependencyGraph(toProblem) = s;
60 end
61
62 function obj = addSubProblem(obj, name, variables, after)
63 if isempty(variables), vt = 'custom'; else, vt = variables{1}.getVariableType(); end
64 obj.subproblems{end+1} = opt.SubProblem(name, vt, variables);
65 if nargin >= 4 && ~isempty(after)
66 for i = 1:numel(after), obj.setDependency(after{i}, name); end
67 end
68 end
69
70 function ordered = getExecutionOrder(obj)
71 if obj.dependencyGraph.Count == 0
72 ordered = obj.subproblems; return;
73 end
74 names = cellfun(@(sp) sp.name, obj.subproblems, 'UniformOutput', false);
75 indeg = containers.Map(names, num2cell(zeros(1, numel(names))));
76 adj = containers.Map('KeyType','char','ValueType','any');
77 for i = 1:numel(names), adj(names{i}) = {}; end
78 tks = keys(obj.dependencyGraph);
79 for i = 1:numel(tks)
80 to = tks{i}; froms = obj.dependencyGraph(to);
81 for j = 1:numel(froms)
82 from = froms{j};
83 if isKey(adj, from) && isKey(indeg, to)
84 a = adj(from); a{end+1} = to; adj(from) = a; %#ok<AGROW>
85 indeg(to) = indeg(to) + 1;
86 end
87 end
88 end
89 q = {};
90 for i = 1:numel(names), if indeg(names{i}) == 0, q{end+1} = names{i}; end; end %#ok<AGROW>
91 orderNames = {}; head = 1;
92 while head <= numel(q)
93 n = q{head}; head = head + 1;
94 orderNames{end+1} = n; %#ok<AGROW>
95 succ = adj(n);
96 for j = 1:numel(succ)
97 m = succ{j}; indeg(m) = indeg(m) - 1;
98 if indeg(m) == 0, q{end+1} = m; end %#ok<AGROW>
99 end
100 end
101 if numel(orderNames) ~= numel(obj.subproblems)
102 ordered = obj.subproblems; return; % cycle: fall back
103 end
104 byName = containers.Map(names, obj.subproblems);
105 ordered = cell(1, numel(orderNames));
106 for i = 1:numel(orderNames), ordered{i} = byName(orderNames{i}); end
107 end
108
109 function result = solveSequential(obj, maxCycles, tolerance)
110 if nargin < 2, maxCycles = 10; end
111 if nargin < 3, tolerance = 0.01; end
112 t0 = tic;
113 result = opt.WorkflowResult();
114 if isempty(obj.subproblems)
115 result.converged = true; return;
116 end
117 ordered = obj.getExecutionOrder();
118 fixedValues = containers.Map('KeyType','char','ValueType','any');
119 prevObjective = inf;
120 for cycle = 1:maxCycles
121 for si = 1:numel(ordered)
122 sp = ordered{si};
123 partial = obj.createPartialProblem(sp, fixedValues);
124 spResult = opt.LineOptSolver(partial, obj.solverOptions).solve();
125 spr = opt.SubProblemResult(sp.name, spResult);
126 fk = keys(fixedValues);
127 for i = 1:numel(fk), spr.variablesFixed(fk{i}) = fixedValues(fk{i}); end
128 result.subproblemResults(sp.name) = spr;
129 vk = keys(spResult.variableValues);
130 for i = 1:numel(vk), fixedValues(vk{i}) = spResult.variableValues(vk{i}); end
131 end
132 currentObjective = obj.evaluateFullObjective(fixedValues);
133 result.objectiveHistory(end+1) = currentObjective;
134 if abs(currentObjective - prevObjective) < tolerance
135 result.converged = true; break;
136 end
137 prevObjective = currentObjective;
138 result.cyclesCompleted = cycle;
139 end
140 if isempty(result.objectiveHistory)
141 result.finalObjective = inf;
142 else
143 result.finalObjective = result.objectiveHistory(end);
144 end
145 fk = keys(fixedValues);
146 for i = 1:numel(fk), result.finalVariableValues(fk{i}) = fixedValues(fk{i}); end
147 result.totalSolveTime = toc(t0);
148 end
149
150 function result = solveHierarchical(obj)
151 result = obj.solveSequential(1, 0.0);
152 end
153
154 function partial = createPartialProblem(obj, subproblem, fixedValues)
155 partial = opt.OptimizationProblem(obj.problem.getModel());
156 for i = 1:numel(subproblem.variables), partial.addVariable(subproblem.variables{i}); end
157 subNames = subproblem.getVariableNames();
158 fixedPairs = {};
159 vars = obj.problem.getVariables();
160 for i = 1:numel(vars)
161 nm = vars{i}.getName();
162 if ~any(strcmp(nm, subNames)) && isKey(fixedValues, nm)
163 fixedPairs{end+1} = {vars{i}, fixedValues(nm)}; %#ok<AGROW>
164 end
165 end
166 partial.setFixedVariables(fixedPairs);
167 partial.setObjective(obj.problem.getObjective());
168 cons = obj.problem.getConstraints();
169 for i = 1:numel(cons), partial.addConstraint(cons{i}); end
170 scen = obj.problem.getScenarios();
171 for i = 1:numel(scen), partial.addScenario(scen{i}{1}, scen{i}{2}); end
172 end
173
174 function value = evaluateFullObjective(obj, variableValues)
175 penaltyWeight = obj.solverOptions.penaltyWeight;
176 evaluator = opt.LineEvaluator(obj.problem.getModel(), obj.problem.getVariables(), {});
177 res = evaluator.evaluateValues(variableValues);
178 if ~res.feasible, value = inf; return; end
179 objective = obj.problem.getObjective();
180 value = objective.evaluateWithPenalty(res, variableValues, penaltyWeight);
181 cons = obj.problem.getConstraints();
182 for i = 1:numel(cons)
183 value = value + cons{i}.evaluate(res, variableValues) * penaltyWeight;
184 end
185 end
186 end
187end