xref: /haiku/src/libs/linprog/ActiveSetSolver.cpp (revision 1b6bc2675fe3691538c8764ab016593f3b06ca53)
1 /*
2  * Copyright 2010 - 2011, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Clemens Zeidler <haiku@clemens-zeidler.de>
7  */
8 
9 
10 #include "ActiveSetSolver.h"
11 
12 #include <algorithm>
13 #include <stdio.h>
14 
15 #include "LayoutOptimizer.h"
16 
17 
18 //#define DEBUG_ACTIVE_SOLVER
19 
20 #ifdef DEBUG_ACTIVE_SOLVER
21 #include <stdio.h>
22 #define TRACE(x...) printf(x)
23 #else
24 #define TRACE(x...) /* nothing */
25 #endif
26 
27 
28 using namespace LinearProgramming;
29 using namespace std;
30 
31 
32 EquationSystem::EquationSystem(int32 rows, int32 columns)
33 	:
34 	fRows(rows),
35 	fColumns(columns)
36 {
37 	fMatrix = allocate_matrix(fRows, fColumns);
38 	fB = new double[fColumns];
39 	// better init all values to prevent side cases where not all variables
40 	// needed to solve the problem, coping theses values to the results could
41 	// cause problems
42 	for (int i = 0; i < fColumns; i++)
43 		fB[i] = 0;
44 	zero_matrix(fMatrix, fRows, fColumns);
45 
46 	fRowIndices = new int32[fRows];
47 	fColumnIndices = new int32[fColumns];
48 	for (int i = 0; i < fRows; i++)
49 		fRowIndices[i] = i;
50 	for (int i = 0; i < fColumns; i++)
51 		fColumnIndices[i] = i;
52 }
53 
54 
55 EquationSystem::~EquationSystem()
56 {
57 	free_matrix(fMatrix);
58 	delete[] fB;
59 	delete[] fRowIndices;
60 	delete[] fColumnIndices;
61 }
62 
63 
64 void
65 EquationSystem::SetRows(int32 rows)
66 {
67 	fRows = rows;
68 }
69 
70 
71 int32
72 EquationSystem::Rows()
73 {
74 	return fRows;
75 }
76 
77 
78 int32
79 EquationSystem::Columns()
80 {
81 	return fColumns;
82 }
83 
84 
85 double&
86 EquationSystem::A(int32 row, int32 column)
87 {
88 	return fMatrix[fRowIndices[row]][fColumnIndices[column]];
89 }
90 
91 
92 double&
93 EquationSystem::B(int32 row)
94 {
95 	return fB[row];
96 }
97 
98 
99 void
100 EquationSystem::Results(double* results, int32 size)
101 {
102 	for (int i = 0; i < size; i++)
103 		results[i] = 0;
104 	for (int i = 0; i < fColumns; i++) {
105 		int32 index = fColumnIndices[i];
106 		if (index < fRows)
107 			results[index] = fB[i];
108 	}
109 }
110 
111 
112 void
113 EquationSystem::SwapColumn(int32 i, int32 j)
114 {
115 	swap(fColumnIndices[i], fColumnIndices[j]);
116 }
117 
118 
119 void
120 EquationSystem::SwapRow(int32 i, int32 j)
121 {
122 	swap(fRowIndices[i], fRowIndices[j]);
123 	swap(fB[i], fB[j]);
124 }
125 
126 
127 bool
128 EquationSystem::GaussianElimination()
129 {
130 	// basic solve
131 	for (int i = 0; i < fRows; i++) {
132 		// find none zero element
133 		int swapRow = -1;
134 		for (int r = i; r < fRows; r++) {
135 			double& value = fMatrix[fRowIndices[r]][fColumnIndices[i]];
136 			if (fuzzy_equals(value, 0))
137 				continue;
138 			swapRow = r;
139 			break;
140 		}
141 		if (swapRow == -1) {
142 			int swapColumn = -1;
143 			for (int c = i + 1; c < fColumns; c++) {
144 				double& value = fMatrix[fRowIndices[i]][fColumnIndices[c]];
145 				if (fuzzy_equals(value, 0))
146 					continue;
147 				swapRow = i;
148 				swapColumn = c;
149 				break;
150 			}
151 			if (swapColumn == -1) {
152 				printf("can't solve column %i\n", i);
153 				return false;
154 			}
155 			SwapColumn(i, swapColumn);
156 		}
157 		if (i != swapRow)
158 			SwapRow(i, swapRow);
159 
160 		// normalize
161 		_EliminateColumn(i, i + 1, fRows - 1);
162 	}
163 	return true;
164 }
165 
166 
167 bool
168 EquationSystem::GaussJordan()
169 {
170 	if (!GaussianElimination())
171 		return false;
172 
173 	for (int32 i = fRows - 1; i >= 0; i--)
174 		_EliminateColumn(i, 0, i - 1);
175 
176 	return true;
177 }
178 
179 
180 void
181 EquationSystem::GaussJordan(int32 i)
182 {
183 	_EliminateColumn(i, 0, fRows - 1);
184 }
185 
186 
187 void
188 EquationSystem::RemoveLinearlyDependentRows()
189 {
190 	double oldB[fRows];
191 	for (int r = 0; r < fRows; r++)
192 		oldB[r] = fB[r];
193 
194 	double** temp = allocate_matrix(fRows, fColumns);
195 	bool independentRows[fRows];
196 
197 	// copy to temp
198 	copy_matrix(fMatrix, temp, fRows, fColumns);
199 	int nIndependent = compute_dependencies(temp, fRows, fColumns,
200 		independentRows);
201 	if (nIndependent == fRows)
202 		return;
203 
204 	// remove the rows
205 	for (int i = 0; i < fRows; i++) {
206 		if (!independentRows[i]) {
207 			int lastDepRow = -1;
208 			for (int d = fRows - 1; d > i; d--) {
209 				if (independentRows[d]) {
210 					lastDepRow = d;
211 					break;
212 				}
213 			}
214 			if (lastDepRow < 0)
215 				break;
216 			SwapRow(i, lastDepRow);
217 			fRows--;
218 		}
219 	}
220 	fRows = nIndependent;
221 
222 	free_matrix(temp);
223 }
224 
225 
226 void
227 EquationSystem::RemoveUnusedVariables()
228 {
229 	for (int c = 0; c < fColumns; c++) {
230 		bool used = false;
231 		for (int r = 0; r < fRows; r++) {
232 			if (!fuzzy_equals(fMatrix[r][fColumnIndices[c]], 0)) {
233 				used = true;
234 				break;
235 			}
236 		}
237 		if (used)
238 			continue;
239 
240 		//MoveColumnRight(c, fColumns - 1);
241 		SwapColumn(c, fColumns - 1);
242 		fColumns--;
243 		c--;
244 	}
245 }
246 
247 
248 void
249 EquationSystem::MoveColumnRight(int32 i, int32 target)
250 {
251 	int32 index = fColumnIndices[i];
252 	for (int c = i; c < target; c++)
253 		fColumnIndices[c] = fColumnIndices[c + 1];
254 	fColumnIndices[target] = index;
255 }
256 
257 
258 void
259 EquationSystem::Print()
260 {
261 	for (int m = 0; m < fRows; m++) {
262 		for (int n = 0; n < fColumns; n++)
263 			printf("%.1f ", fMatrix[fRowIndices[m]][fColumnIndices[n]]);
264 		printf("= %.1f\n", fB[m]);
265 	}
266 }
267 
268 
269 void
270 EquationSystem::_EliminateColumn(int32 column, int32 startRow, int32 endRow)
271 {
272 	double value = fMatrix[fRowIndices[column]][fColumnIndices[column]];
273 	if (value != 1.) {
274 		for (int j = column; j < fColumns; j++)
275 			fMatrix[fRowIndices[column]][fColumnIndices[j]] /= value;
276 		fB[column] /= value;
277 	}
278 
279 	for (int r = startRow; r < endRow + 1; r++) {
280 		if (r == column)
281 			continue;
282 		double q = -fMatrix[fRowIndices[r]][fColumnIndices[column]];
283 		// don't need to do anything, since matrix is typically sparse this
284 		// should save some work
285 		if (fuzzy_equals(q, 0))
286 			continue;
287 		for (int c = column; c < fColumns; c++)
288 			fMatrix[fRowIndices[r]][fColumnIndices[c]]
289 				+= fMatrix[fRowIndices[column]][fColumnIndices[c]] * q;
290 
291 		fB[r] += fB[column] * q;
292 	}
293 }
294 
295 
296 ActiveSetSolver::ActiveSetSolver(LinearSpec* linearSpec)
297 	:
298 	QPSolverInterface(linearSpec),
299 
300 	fVariables(linearSpec->UsedVariables()),
301 	fConstraints(linearSpec->Constraints())
302 {
303 
304 }
305 
306 
307 ActiveSetSolver::~ActiveSetSolver()
308 {
309 
310 }
311 
312 
313 /* Using algorithm found in:
314 Solving Inequalities and Proving Farkas's Lemma Made Easy
315 David Avis and Bohdan Kaluzny
316 The American Mathematical Monthly
317 Vol. 111, No. 2 (Feb., 2004), pp. 152-157 */
318 static bool
319 solve(EquationSystem& system)
320 {
321 	// basic solve
322 	if (!system.GaussJordan())
323 		return false;
324 
325 	bool done = false;
326 	while (!done) {
327 		double smallestB = HUGE_VALF;
328 		int smallestBRow = -1;
329 		for (int row = 0; row < system.Rows(); row++) {
330 			if (system.B(row) > 0 || fuzzy_equals(system.B(row), 0))
331 				continue;
332 
333 			double bValue = fabs(system.B(row));
334 			if (bValue < smallestB) {
335 				smallestB = bValue;
336 				smallestBRow = row;
337 			}
338 		}
339 		if (smallestBRow == -1) {
340 			done = true;
341 			break;
342 		}
343 
344 		int negValueCol = -1;
345 		for (int col = system.Rows(); col < system.Columns(); col++) {
346 			double value = system.A(smallestBRow, col);
347 			if (value > 0 || fuzzy_equals(value, 0))
348 				continue;
349 			negValueCol = col;
350 			break;
351 		}
352 		if (negValueCol == -1) {
353 			printf("can't solve\n");
354 			return false;
355 		}
356 
357 		system.SwapColumn(smallestBRow, negValueCol);
358 
359 		// eliminate
360 		system.GaussJordan(smallestBRow);
361 	}
362 
363 	return true;
364 }
365 
366 
367 ResultType
368 ActiveSetSolver::Solve()
369 {
370 	int32 nConstraints = fConstraints.CountItems();
371 	int32 nVariables = fVariables.CountItems();
372 
373 	if (nVariables > nConstraints) {
374 		printf("More variables then constraints! vars: %i, constraints: %i\n",
375 			(int)nVariables, (int)nConstraints);
376 		return kInfeasible;
377 	}
378 
379 	/* First find an initial solution and then optimize it using the active set
380 	method. */
381 	EquationSystem system(nConstraints, nVariables + nConstraints);
382 
383 	int32 slackIndex = nVariables;
384 	// setup constraint matrix and add slack variables if necessary
385 	int32 rowIndex = 0;
386 	for (int32 c = 0; c < nConstraints; c++) {
387 		Constraint* constraint = fConstraints.ItemAt(c);
388 		if (constraint->IsSoft())
389 			continue;
390 		SummandList* leftSide = constraint->LeftSide();
391 		system.B(rowIndex) = constraint->RightSide();
392 		for (int32 sIndex = 0; sIndex < leftSide->CountItems(); sIndex++ ) {
393 			Summand* summand = leftSide->ItemAt(sIndex);
394 			double coefficient = summand->Coeff();
395 			system.A(rowIndex, summand->VariableIndex()) = coefficient;
396 		}
397 		if (constraint->Op() == kLE) {
398 			system.A(rowIndex, slackIndex) = 1;
399 			slackIndex++;
400 		} else if (constraint->Op() == kGE) {
401 			system.A(rowIndex, slackIndex) = -1;
402 			slackIndex++;
403 		}
404 		rowIndex++;
405 	}
406 
407 	system.SetRows(rowIndex);
408 
409 	system.RemoveLinearlyDependentRows();
410 	system.RemoveUnusedVariables();
411 
412 	if (!solve(system))
413 		return kInfeasible;
414 
415 	double results[nVariables + nConstraints];
416 	system.Results(results, nVariables + nConstraints);
417 	TRACE("base system solved\n");
418 
419 	LayoutOptimizer optimizer(fConstraints, nVariables);
420 	optimizer.Solve(results);
421 
422 	// back to the variables
423 	for (int32 i = 0; i < nVariables; i++)
424 		fVariables.ItemAt(i)->SetValue(results[i]);
425 
426 	for (int32 i = 0; i < nVariables; i++)
427 		TRACE("var %f\n", results[i]);
428 
429 	return kOptimal;
430 }
431 
432 
433 bool
434 ActiveSetSolver::VariableAdded(Variable* variable)
435 {
436 	if (fVariableGEConstraints.AddItem(NULL) == false)
437 		return false;
438 	if (fVariableLEConstraints.AddItem(NULL) == false) {
439 		// clean up
440 		int32 count = fVariableGEConstraints.CountItems();
441 		fVariableGEConstraints.RemoveItemAt(count - 1);
442 		return false;
443 	}
444 	return true;
445 }
446 
447 
448 bool
449 ActiveSetSolver::VariableRemoved(Variable* variable)
450 {
451 	fVariableGEConstraints.RemoveItemAt(variable->GlobalIndex());
452 	fVariableLEConstraints.RemoveItemAt(variable->GlobalIndex());
453 	return true;
454 }
455 
456 
457 bool
458 ActiveSetSolver::VariableRangeChanged(Variable* variable)
459 {
460 	double min = variable->Min();
461 	double max = variable->Max();
462 	int32 variableIndex = variable->GlobalIndex();
463 
464 	Constraint* constraintGE = fVariableGEConstraints.ItemAt(variableIndex);
465 	Constraint* constraintLE = fVariableLEConstraints.ItemAt(variableIndex);
466 	if (constraintGE == NULL && min > -20000) {
467 		constraintGE = fLinearSpec->AddConstraint(1, variable, kGE, 0);
468 		if (constraintGE == NULL)
469 			return false;
470 		fVariableGEConstraints.RemoveItemAt(variableIndex);
471 		fVariableGEConstraints.AddItem(constraintGE, variableIndex);
472 	}
473 	if (constraintLE == NULL && max < 20000) {
474 		constraintLE = fLinearSpec->AddConstraint(1, variable, kLE, 20000);
475 		if (constraintLE == NULL)
476 			return false;
477 		fVariableLEConstraints.RemoveItemAt(variableIndex);
478 		fVariableLEConstraints.AddItem(constraintLE, variableIndex);
479 	}
480 
481 	if (constraintGE)
482 		constraintGE->SetRightSide(min);
483 	if (constraintLE)
484 		constraintLE->SetRightSide(max);
485 	return true;
486 }
487 
488 
489 bool
490 ActiveSetSolver::ConstraintAdded(Constraint* constraint)
491 {
492 	return QPSolverInterface::ConstraintAdded(constraint);
493 }
494 
495 
496 bool
497 ActiveSetSolver::ConstraintRemoved(Constraint* constraint)
498 {
499 	return QPSolverInterface::ConstraintRemoved(constraint);
500 }
501 
502 
503 bool
504 ActiveSetSolver::LeftSideChanged(Constraint* constraint)
505 {
506 	return true;
507 }
508 
509 
510 bool
511 ActiveSetSolver::RightSideChanged(Constraint* constraint)
512 {
513 	return true;
514 }
515 
516 
517 bool
518 ActiveSetSolver::OperatorChanged(Constraint* constraint)
519 {
520 	return true;
521 }
522 
523 
524 bool
525 ActiveSetSolver::SaveModel(const char* fileName)
526 {
527 	return false;
528 }
529 
530 
531 BSize
532 ActiveSetSolver::MinSize(Variable* width, Variable* height)
533 {
534 	ConstraintList softConstraints;
535 	_RemoveSoftConstraint(softConstraints);
536 
537 	Constraint* heightConstraint = fLinearSpec->AddConstraint(1, height,
538 		kEQ, 0, 5, 5);
539 	Constraint* widthConstraint = fLinearSpec->AddConstraint(1, width,
540 		kEQ, 0, 5, 5);
541 	ResultType result = Solve();
542 	fLinearSpec->RemoveConstraint(heightConstraint);
543 	fLinearSpec->RemoveConstraint(widthConstraint);
544 
545 	_AddSoftConstraint(softConstraints);
546 
547 	if (result == kUnbounded)
548 		return kMinSize;
549 	if (result != kOptimal)
550 		printf("Could not solve the layout specification (%d). ", result);
551 
552 	return BSize(width->Value(), height->Value());
553 }
554 
555 
556 BSize
557 ActiveSetSolver::MaxSize(Variable* width, Variable* height)
558 {
559 	ConstraintList softConstraints;
560 	_RemoveSoftConstraint(softConstraints);
561 
562 	const double kHugeValue = 32000;
563 	Constraint* heightConstraint = fLinearSpec->AddConstraint(1, height,
564 		kEQ, kHugeValue, 5, 5);
565 	Constraint* widthConstraint = fLinearSpec->AddConstraint(1, width,
566 		kEQ, kHugeValue, 5, 5);
567 	ResultType result = Solve();
568 	fLinearSpec->RemoveConstraint(heightConstraint);
569 	fLinearSpec->RemoveConstraint(widthConstraint);
570 
571 	_AddSoftConstraint(softConstraints);
572 
573 	if (result == kUnbounded)
574 		return kMaxSize;
575 	if (result != kOptimal)
576 		printf("Could not solve the layout specification (%d). ", result);
577 
578 	return BSize(width->Value(), height->Value());
579 }
580 
581 
582 void
583 ActiveSetSolver::_RemoveSoftConstraint(ConstraintList& list)
584 {
585 	ConstraintList allConstraints = fLinearSpec->Constraints();
586 	for (int i = 0; i < allConstraints.CountItems(); i++) {
587 		Constraint* constraint = allConstraints.ItemAt(i);
588 		if (!constraint->IsSoft())
589 			continue;
590 
591 		if (fLinearSpec->RemoveConstraint(constraint, false) == true)
592 			list.AddItem(constraint);
593 	}
594 }
595 
596 
597 void
598 ActiveSetSolver::_AddSoftConstraint(const ConstraintList& list)
599 {
600 	for (int i = 0; i < list.CountItems(); i++) {
601 		Constraint* constraint = list.ItemAt(i);
602 		// at least don't leak it
603 		if (fLinearSpec->AddConstraint(constraint) == false)
604 			delete constraint;
605 	}
606 }
607 
608