forked from opencobra/cobratoolbox
-
Notifications
You must be signed in to change notification settings - Fork 0
/
optimizeCbModel.m
328 lines (305 loc) · 10.7 KB
/
optimizeCbModel.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
function FBAsolution = optimizeCbModel(model,osenseStr, minNorm, allowLoops)
%optimizeCbModel Solve a flux balance analysis problem
%
% Solves LP problems of the form: max/min c'*v
% subject to S*v = b
% lb <= v <= ub
% FBAsolution = optimizeCbModel(model,osenseStr,minNormFlag)
%
%INPUT
% model (the following fields are required - others can be supplied)
% S Stoichiometric matrix
% b Right hand side = dx/dt
% c Objective coefficients
% lb Lower bounds
% ub Upper bounds
%
%OPTIONAL INPUTS
% osenseStr Maximize ('max')/minimize ('min') (opt, default = 'max')
%
% minNorm {(0), 'one', > 0 , n x 1 vector}, where [m,n]=size(S);
% 0 Default, normal LP
% 'one' Minimise the Taxicab Norm using LP.
% min |v|
% s.t. S*v = b
% c'v = f
% lb <= v <= ub
% -----
% The remaining options work only with a valid QP solver:
% -----
% > 0 Minimises the Euclidean Norm of internal fluxes.
% Typically 1e-6 works well.
% min ||v||
% s.t. S*v = b
% c'v = f
% lb <= v <= ub
% n x 1 Forms the diagonal of positive definiate
% matrix F in the quadratic program
% min 0.5*v'*F*v
% st. S*v = b
% c'*v = f
% lb <= v <= ub
%
% allowLoops {0,(1)} If false, then instead of a conventional FBA,
% the solver will run an MILP version which does not allow
% loops in the final solution. Default is true.
% Runs much slower when set to false.
% See addLoopLawConstraints.m to for more info.
%
%OUTPUT
% FBAsolution
% f Objective value
% x Primal
% y Dual
% w Reduced costs
% s Slacks
% stat Solver status in standardized form
% 1 Optimal solution
% 2 Unbounded solution
% 0 Infeasible
% -1 No solution reported (timelimit, numerical problem etc)
%
% Markus Herrgard 9/16/03
% Ronan Fleming 4/25/09 Option to minimises the Euclidean Norm of internal
% fluxes using 'cplex_direct' solver
% Ronan Fleming 7/27/09 Return an error if any imputs are NaN
% Ronan Fleming 10/24/09 Fixed 'E' for all equality constraints
% Jan Schellenberger MILP option to remove flux around loops
% Ronan Fleming 12/07/09 Reworked minNorm parameter option to allow
% the full range of approaches for getting
% rid of net flux around loops.
% Jan Schellenberger 2/3/09 fixed bug with .f being set incorrectly
% when minNorm was set.
% Nathan Lewis 12/2/10 Modified code to allow for inequality
% constraints.
% Ronan Fleming 12/03/10 Minor changes to the internal handling of global parameters.
%% Process arguments and set up problem
if exist('osenseStr', 'var')
if isempty(osenseStr)
osenseStr = 'max';
end
else
osenseStr = 'max';
end
if exist('minNorm', 'var')
if isempty(minNorm)
minNorm = false;
changeOK = changeCobraSolverParams('LP','minNorm',minNorm);
else
changeOK = changeCobraSolverParams('LP','minNorm',minNorm);
end
else
minNorm = false;
changeOK = changeCobraSolverParams('LP','minNorm',minNorm);
end
if exist('allowLoops', 'var')
if isempty(allowLoops)
allowLoops = true;
end
else
allowLoops = true;
end
[minNorm, printLevel, primalOnlyFlag, saveInput] = getCobraSolverParams('LP',{'minNorm','printLevel','primalOnly','saveInput'});
% if exist('minNorm', 'var')
% if isempty(minNorm)
% minNorm = false;
% end
% else
% minNorm = false;
% end
% if exist('allowLoops', 'var')
% if isempty(allowLoops)
% allowLoops = true;
% end
% else
% allowLoops = true;
% end
%
%
% global CBT_LP_PARAMS
% if (exist('CBT_LP_PARAMS', 'var'))
% if isfield(CBT_LP_PARAMS, 'objTol')
% tol = CBT_LP_PARAMS.objTol;
% else
% tol = 1e-6;
% end
% if isfield(CBT_LP_PARAMS, 'primalOnly')
% primalOnlyFlag = CBT_LP_PARAMS.primalOnly;
% else
% primalOnlyFlag = false;
% end
% if isfield(CBT_LP_PARAMS, 'printLevel')
% printLevel = CBT_LP_PARAMS.printLevel;
% else
% printLevel = 0;
% end
% else
% tol = 1e-6;
% primalOnlyFlag = false;
% printLevel = 0;
% end
% Figure out objective sense
if strcmpi(osenseStr,'max')
LPproblem.osense = -1;
else
LPproblem.osense = +1;
end
% this is dangerous... if model does not have S, it should not be called in
% this function.
% if ~isfield(model,'S')
% model.S=model.A;
% end
[nMets,nRxns] = size(model.S);
% add csense
%Doing this makes csense a double array. Totally smart design move.
%LPproblem.csense = [];
if ~isfield(model,'csense')
% If csense is not declared in the model, assume that all
% constraints are equalities.
LPproblem.csense(1:nMets,1) = 'E';
else % if csense is in the model, move it to the lp problem structure
if length(model.csense)~=nMets,
warning('Length of csense is invalid! Defaulting to equality constraints.')
LPproblem.csense(1:nMets,1) = 'E';
else
model.csense = columnVector(model.csense);
LPproblem.csense = model.csense;
end
end
% Fill in the RHS vector if not provided
if (~isfield(model,'b'))
LPproblem.b = zeros(size(model.S,1),1);
else
LPproblem.b = model.b;
end
% Rest of the LP problem
LPproblem.A = model.S;
LPproblem.c = model.c;
LPproblem.lb = model.lb;
LPproblem.ub = model.ub;
%Double check that all inputs are valid:
if ~(verifyCobraProblem(LPproblem, [], [], false) == 1)
warning('invalid problem');
return;
end
%%
t1 = clock;
% Solve initial LP
if allowLoops
solution = solveCobraLP(LPproblem);
else
MILPproblem = addLoopLawConstraints(LPproblem, model, 1:nRxns);
solution = solveCobraMILP(MILPproblem);
end
if (solution.stat ~= 1) % check if initial solution was successful.
if printLevel>0
warning('Optimal solution was not found');
end
FBAsolution.f = 0;
FBAsolution.x = [];
FBAsolution.stat = solution.stat;
FBAsolution.origStat = solution.origStat;
FBAsolution.solver = solution.solver;
FBAsolution.time = etime(clock, t1);
return;
end
objective = solution.obj; % save for later use.
if strcmp(minNorm, 'one')
% Minimize the absolute value of fluxes to 'avoid' loopy solutions
% Solve secondary LP to minimize one-norm of |v|
% Set up the optimization problem
% min sum(delta+ + delta-)
% 1: S*v1 = 0
% 3: delta+ >= -v1
% 4: delta- >= v1
% 5: c'v1 >= f (optimal value of objective)
%
% delta+,delta- >= 0
LPproblem2.A = [model.S sparse(nMets,2*nRxns);
speye(nRxns,nRxns) speye(nRxns,nRxns) sparse(nRxns,nRxns);
-speye(nRxns,nRxns) sparse(nRxns,nRxns) speye(nRxns,nRxns);
model.c' sparse(1,2*nRxns)];
LPproblem2.c = [zeros(nRxns,1);ones(2*nRxns,1)];
LPproblem2.lb = [model.lb;zeros(2*nRxns,1)];
LPproblem2.ub = [model.ub;10000*ones(2*nRxns,1)];
LPproblem2.b = [LPproblem.b;zeros(2*nRxns,1);solution.obj];
if ~isfield(model,'csense')
% If csense is not declared in the model, assume that all
% constraints are equalities.
LPproblem2.csense(1:nMets) = 'E';
else % if csense is in the model, move it to the lp problem structure
if length(model.csense)~=nMets,
warning('Length of csense is invalid! Defaulting to equality constraints.')
LPproblem2.csense(1:nMets) = 'E';
else
LPproblem2.csense = columnVector(model.csense);
end
end
LPproblem2.csense((nMets+1):(nMets+2*nRxns)) = 'G';
LPproblem2.csense(nMets+2*nRxns+1) = 'G';
LPproblem2.csense = columnVector(LPproblem2.csense);
LPproblem2.osense = 1;
% Re-solve the problem
if allowLoops
solution = solveCobraLP(LPproblem2); %,printLevel,minNorm);
solution.dual = []; % slacks and duals will not be valid for this computation.
solution.rcost = [];
else
MILPproblem2 = addLoopLawConstraints(LPproblem, model, 1:nRxns);
solution = solveCobraMILP(MILPproblem2);
end
elseif length(minNorm)> 1 || minNorm > 0
% quadratic minimization of the norm.
% set previous optimum as constraint.
LPproblem.A = [LPproblem.A;
LPproblem.c'];
LPproblem.csense(end+1) = 'E';
if nnz(LPproblem.c)>1
error('Code assumes only one non-negative coefficient in linear part of objective');
end
LPproblem.b = [LPproblem.b;solution.full(LPproblem.c~=0)];
LPproblem.c = zeros(size(LPproblem.c)); % no need for c anymore.
%Minimise Euclidean norm using quadratic programming
if length(minNorm)==1
minNorm=ones(nRxns,1)*minNorm;
end
LPproblem.F = spdiags(minNorm,0,nRxns,nRxns);
%quadratic optimization
if allowLoops
solution = solveCobraQP(LPproblem);
else
MIQPproblem = addLoopLawConstraints(LPproblem, model, 1:nRxns);
solution = solveCobraMIQP(MIQPproblem);
end
% if isempty(solution.full)
% % QP problem did not work. This will return empty structure later.
% else
% %dont include dual variable to additional constraint
% %solution.dual=solution.dual(1:end-1,1);
% end
end
% Store results
if (solution.stat == 1)
%solution found.
FBAsolution.x = solution.full(1:nRxns);
%this line IS necessary.
FBAsolution.f = model.c'*solution.full(1:nRxns); %objective from original optimization problem.
if abs(FBAsolution.f - objective) > .01
display('warning: objective appears to have changed while performing secondary optimization (minNorm)');
end
if (~primalOnlyFlag && allowLoops && any(~minNorm)) % rcost/dual only correct if not doing minNorm
FBAsolution.y = solution.dual;
FBAsolution.w = solution.rcost;
end
else
%some sort of error occured.
if printLevel>0
warning('Optimal solution was not found');
end
FBAsolution.f = 0;
FBAsolution.x = [];
end
FBAsolution.stat = solution.stat;
FBAsolution.origStat = solution.origStat;
FBAsolution.solver = solution.solver;
FBAsolution.time = etime(clock, t1);