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