2 * Copyright 2007, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
3 * Copyright 2010, Clemens Zeidler <haiku@clemens-zeidler.de>
4 * Distributed under the terms of the MIT License.
8 #include "LayoutOptimizer.h"
15 #include <AutoDeleter.h>
18 //#define TRACE_LAYOUT_OPTIMIZER 1
19 #if TRACE_LAYOUT_OPTIMIZER
20 # define TRACE(format...) printf(format)
21 # define TRACE_ONLY(x) x
23 # define TRACE(format...)
24 # define TRACE_ONLY(x)
26 #define TRACE_ERROR(format...) fprintf(stderr, format)
33 bool is_soft(Constraint
* constraint
)
35 if (constraint
->Op() != LinearProgramming::kEQ
)
37 if (constraint
->PenaltyNeg() > 0. || constraint
->PenaltyPos() > 0.)
43 /*! \class BPrivate::Layout::LayoutOptimizer
45 Given a set of layout constraints, a feasible solution, and a desired
46 (non-)solution this class finds an optimal solution. The optimization
47 criterion is to minimize the norm of the difference to the desired
50 It does so by implementing an active set method algorithm. The basic idea
51 is to start with the subset of the constraints that are barely satisfied by
52 the feasible solution, i.e. including all equality constraints and those
53 inequality constraints that are still satisfied, if restricted to equality
54 constraints. This set is called active set, the contained constraints active
57 Considering all of the active constraints equality constraints a new
58 solution is computed, which still satisfies all those equality constraints
59 and is optimal with respect to the optimization criterion.
61 If the new solution equals the previous one, we find the inequality
62 constraint that, by keeping it in the active set, prevents us most from
63 further optimizing the solution. If none really does, we're done, having
64 found the globally optimal solution. Otherwise we remove the found
65 constraint from the active set and try again.
67 If the new solution does not equal the previous one, it might violate one
68 or more of the inactive constraints. If that is the case, we add the
69 most-violated constraint to the active set and adjust the new solution such
70 that barely satisfies that constraint. Otherwise, we don't adjust the
71 computed solution. With the adjusted respectively unadjusted solution
72 we enter the next iteration, i.e. by computing a new optimal solution with
73 respect to the active set.
77 // #pragma mark - vector and matrix operations
82 is_zero(double* x
, int n
)
84 for (int i
= 0; i
< n
; i
++) {
85 if (!fuzzy_equals(x
[i
], 0))
95 add_vectors(double* x
, const double* y
, int n
)
97 for (int i
= 0; i
< n
; i
++)
102 // add_vectors_scaled
104 add_vectors_scaled(double* x
, const double* y
, double scalar
, int n
)
106 for (int i
= 0; i
< n
; i
++)
107 x
[i
] += y
[i
] * scalar
;
113 negate_vector(double* x
, int n
)
115 for (int i
= 0; i
< n
; i
++)
122 allocate_matrix(int m
, int n
)
124 double** matrix
= new(nothrow
) double*[m
];
128 double* values
= new(nothrow
) double[m
* n
];
134 double* row
= values
;
135 for (int i
= 0; i
< m
; i
++, row
+= n
)
144 free_matrix(double** matrix
)
153 // multiply_matrix_vector
158 multiply_matrix_vector(const double* const* A
, const double* x
, int m
, int n
,
161 for (int i
= 0; i
< m
; i
++) {
163 for (int k
= 0; k
< n
; k
++)
164 sum
+= A
[i
][k
] * x
[k
];
174 multiply_matrices(const double* const* a
, const double* const* b
, double** c
,
177 for (int i
= 0; i
< m
; i
++) {
178 for (int j
= 0; j
< l
; j
++) {
180 for (int k
= 0; k
< n
; k
++)
181 sum
+= a
[i
][k
] * b
[k
][j
];
190 transpose_matrix(const double* const* A
, double** Atrans
, int m
, int n
)
192 for (int i
= 0; i
< m
; i
++) {
193 for (int k
= 0; k
< n
; k
++)
194 Atrans
[k
][i
] = A
[i
][k
];
201 zero_matrix(double** A
, int m
, int n
)
203 for (int i
= 0; i
< m
; i
++) {
204 for (int k
= 0; k
< n
; k
++)
212 copy_matrix(const double* const* A
, double** B
, int m
, int n
)
214 for (int i
= 0; i
< m
; i
++) {
215 for (int k
= 0; k
< n
; k
++)
221 // #pragma mark - algorithms
225 print_system(double** a
, int n
, double* b
)
227 for (int i
= 0; i
< n
; i
++) {
228 for (int j
= 0; j
< n
; j
++) {
229 printf("%.1f ", a
[i
][j
]);
231 printf("= %f\n", b
[i
]);
238 solve(double** a
, int n
, double* b
)
240 // index array for row permutation
241 // Note: We could eliminate it, if we would permutate the row pointers of a.
243 for (int i
= 0; i
< n
; i
++)
246 // forward elimination
247 for (int i
= 0; i
< n
- 1; i
++) {
250 double pivotValue
= fabs(a
[indices
[i
]][i
]);
251 for (int j
= i
+ 1; j
< n
; j
++) {
252 int index
= indices
[j
];
253 double value
= fabs(a
[index
][i
]);
254 if (value
> pivotValue
) {
260 if (fuzzy_equals(pivotValue
, 0)) {
261 TRACE_ERROR("solve(): matrix is not regular\n");
266 swap(indices
[i
], indices
[pivot
]);
267 swap(b
[i
], b
[pivot
]);
272 for (int j
= i
+ 1; j
< n
; j
++) {
273 int index
= indices
[j
];
274 double q
= -a
[index
][i
] / a
[pivot
][i
];
276 for (int k
= i
+ 1; k
< n
; k
++)
277 a
[index
][k
] += a
[pivot
][k
] * q
;
282 // backwards substitution
283 for (int i
= n
- 1; i
>= 0; i
--) {
284 int index
= indices
[i
];
286 for (int j
= i
+ 1; j
< n
; j
++)
287 sum
-= a
[index
][j
] * b
[j
];
289 if (fuzzy_equals(a
[index
][i
], 0))
291 b
[i
] = sum
/ a
[index
][i
];
299 compute_dependencies(double** a
, int m
, int n
,
302 // index array for row permutation
303 // Note: We could eliminate it, if we would permutate the row pointers of a.
305 for (int i
= 0; i
< m
; i
++)
308 // forward elimination
309 int iterations
= (m
> n
? n
: m
);
312 for (; i
< iterations
&& column
< n
; i
++) {
316 double pivotValue
= fabs(a
[indices
[i
]][column
]);
317 for (int j
= i
+ 1; j
< m
; j
++) {
318 int index
= indices
[j
];
319 double value
= fabs(a
[index
][column
]);
320 if (value
> pivotValue
) {
326 if (!fuzzy_equals(pivotValue
, 0))
330 } while (column
< n
);
336 swap(indices
[i
], indices
[pivot
]);
339 independent
[pivot
] = true;
342 for (int j
= i
+ 1; j
< m
; j
++) {
343 int index
= indices
[j
];
344 double q
= -a
[index
][column
] / a
[pivot
][column
];
345 a
[index
][column
] = 0;
346 for (int k
= column
+ 1; k
< n
; k
++)
347 a
[index
][k
] += a
[pivot
][k
] * q
;
353 for (int j
= i
; j
< m
; j
++)
354 independent
[indices
[j
]] = false;
360 // remove_linearly_dependent_rows
362 remove_linearly_dependent_rows(double** A
, double** temp
,
363 bool* independentRows
, int m
, int n
)
366 copy_matrix(A
, temp
, m
, n
);
368 int count
= compute_dependencies(temp
, m
, n
, independentRows
);
374 for (int i
= 0; i
< m
; i
++) {
375 if (independentRows
[i
]) {
377 for (int k
= 0; k
< n
; k
++)
378 A
[index
][k
] = A
[i
][k
];
388 /*! QR decomposition using Householder transformations.
391 qr_decomposition(double** a
, int m
, int n
, double* d
, double** q
)
396 for (int j
= 0; j
< n
; j
++) {
397 // inner product of the first vector x of the (j,j) minor
398 double innerProductU
= 0;
399 for (int i
= j
+ 1; i
< m
; i
++)
400 innerProductU
= innerProductU
+ a
[i
][j
] * a
[i
][j
];
401 double innerProduct
= innerProductU
+ a
[j
][j
] * a
[j
][j
];
402 if (fuzzy_equals(innerProduct
, 0)) {
403 TRACE_ERROR("qr_decomposition(): 0 column %d\n", j
);
407 // alpha (norm of x with opposite signedness of x_1) and thus r_{j,j}
408 double alpha
= (a
[j
][j
] < 0 ? sqrt(innerProduct
) : -sqrt(innerProduct
));
411 double beta
= 1 / (alpha
* a
[j
][j
] - innerProduct
);
413 // u = x - alpha * e_1
417 // left-multiply A_k with Q_k, thus obtaining a row of R and the A_{k+1}
418 // for the next iteration
419 for (int k
= j
+ 1; k
< n
; k
++) {
421 for (int i
= j
; i
< m
; i
++)
422 sum
+= a
[i
][j
] * a
[i
][k
];
425 for (int i
= j
; i
< m
; i
++)
426 a
[i
][k
] += a
[i
][j
] * sum
;
430 innerProductU
+= a
[j
][j
] * a
[j
][j
];
431 double beta2
= -2 / innerProductU
;
433 // right-multiply Q with Q_k
435 // Q * Q_k = Q - 2 * Q * vv^T
437 for (int k
= 0; k
< m
; k
++) {
438 for (int i
= 0; i
< m
; i
++)
439 q
[k
][i
] = beta2
* a
[k
][0] * a
[i
][0];
444 for (int k
= 0; k
< m
; k
++) {
446 for (int i
= j
; i
< m
; i
++)
447 sum
+= q
[k
][i
] * a
[i
][j
];
450 for (int i
= j
; i
< m
; i
++)
451 q
[k
][i
] += sum
* a
[i
][j
];
461 struct MatrixDelete
{
462 inline void operator()(double** matrix
)
467 typedef BPrivate::AutoDeleter
<double*, MatrixDelete
> MatrixDeleter
;
470 // #pragma mark - LayoutOptimizer
474 LayoutOptimizer::LayoutOptimizer(const ConstraintList
& list
,
482 fSoftConstraints(NULL
),
486 SetConstraints(list
, variableCount
);
491 LayoutOptimizer::~LayoutOptimizer()
498 LayoutOptimizer::SetConstraints(const ConstraintList
& list
, int32 variableCount
)
500 fConstraints
= (ConstraintList
*)&list
;
501 int32 constraintCount
= fConstraints
->CountItems();
503 if (fVariableCount
!= variableCount
) {
505 _Init(variableCount
, constraintCount
);
508 zero_matrix(fSoftConstraints
, constraintCount
, fVariableCount
);
509 double rightSide
[constraintCount
];
510 // set up soft constraint matrix
511 for (int32 c
= 0; c
< fConstraints
->CountItems(); c
++) {
512 Constraint
* constraint
= fConstraints
->ItemAt(c
);
513 if (!is_soft(constraint
)) {
518 double negPenalty
= constraint
->PenaltyNeg();
520 weight
+= negPenalty
;
521 double posPenalty
= constraint
->PenaltyPos();
523 weight
+= posPenalty
;
524 if (negPenalty
> 0 && posPenalty
> 0)
527 rightSide
[c
] = _RightSide(constraint
) * weight
;
528 SummandList
* summands
= constraint
->LeftSide();
529 for (int32 s
= 0; s
< summands
->CountItems(); s
++) {
530 Summand
* summand
= summands
->ItemAt(s
);
531 int32 variable
= summand
->Var()->Index();
532 if (constraint
->Op() == LinearProgramming::kLE
)
533 fSoftConstraints
[c
][variable
] = -summand
->Coeff();
535 fSoftConstraints
[c
][variable
] = summand
->Coeff();
536 fSoftConstraints
[c
][variable
] *= weight
;
541 transpose_matrix(fSoftConstraints
, fTemp1
, constraintCount
, fVariableCount
);
542 multiply_matrices(fTemp1
, fSoftConstraints
, fG
, fVariableCount
,
543 constraintCount
, fVariableCount
);
546 multiply_matrix_vector(fTemp1
, rightSide
, fVariableCount
, constraintCount
,
548 negate_vector(fDesired
, fVariableCount
);
556 LayoutOptimizer::InitCheck() const
558 if (!fTemp1
|| !fTemp2
|| !fZtrans
|| !fQ
|| !fSoftConstraints
|| !fG
566 LayoutOptimizer::_ActualValue(Constraint
* constraint
, double* values
) const
568 SummandList
* summands
= constraint
->LeftSide();
570 for (int32 s
= 0; s
< summands
->CountItems(); s
++) {
571 Summand
* summand
= summands
->ItemAt(s
);
572 int32 variable
= summand
->Var()->Index();
573 value
+= values
[variable
] * summand
->Coeff();
575 if (constraint
->Op() == LinearProgramming::kLE
)
582 LayoutOptimizer::_RightSide(Constraint
* constraint
)
584 if (constraint
->Op() == LinearProgramming::kLE
)
585 return -constraint
->RightSide();
586 return constraint
->RightSide();
591 LayoutOptimizer::_MakeEmpty()
595 free_matrix(fZtrans
);
596 free_matrix(fSoftConstraints
);
605 LayoutOptimizer::_Init(int32 variableCount
, int32 nConstraints
)
607 fVariableCount
= variableCount
;
609 int32 maxExtend
= max(variableCount
, nConstraints
);
610 fTemp1
= allocate_matrix(maxExtend
, maxExtend
);
611 fTemp2
= allocate_matrix(maxExtend
, maxExtend
);
612 fZtrans
= allocate_matrix(nConstraints
, fVariableCount
);
613 fSoftConstraints
= allocate_matrix(nConstraints
, fVariableCount
);
614 fQ
= allocate_matrix(nConstraints
, fVariableCount
);
615 fG
= allocate_matrix(nConstraints
, nConstraints
);
617 fDesired
= new(std::nothrow
) double[fVariableCount
];
622 /*! Solves the quadratic program (QP) given by the constraints added via
623 AddConstraint(), the additional constraint \sum_{i=0}^{n-1} x_i = size,
624 and the optimization criterion to minimize
625 \sum_{i=0}^{n-1} (x_i - desired[i])^2.
626 The \a values array must contain a feasible solution when called and will
627 be overwritten with the optimial solution the method computes.
630 LayoutOptimizer::Solve(double* values
)
635 int32 constraintCount
= fConstraints
->CountItems();
637 // allocate the active constraint matrix and its transposed matrix
638 fActiveMatrix
= allocate_matrix(constraintCount
, fVariableCount
);
639 fActiveMatrixTemp
= allocate_matrix(constraintCount
, fVariableCount
);
640 MatrixDeleter
_(fActiveMatrix
);
641 MatrixDeleter
_2(fActiveMatrixTemp
);
642 if (!fActiveMatrix
|| !fActiveMatrixTemp
)
645 bool success
= _Solve(values
);
652 LayoutOptimizer::_Solve(double* values
)
654 int32 constraintCount
= fConstraints
->CountItems();
657 TRACE("constraints:\n");
658 for (int32 i
= 0; i
< constraintCount
; i
++) {
659 TRACE(" %-2ld: ", i
);
660 fConstraints
->ItemAt(i
)->PrintToStream();
664 // our QP is supposed to be in this form:
665 // min_x 1/2x^TGx + x^Td
666 // s.t. a_i^Tx = b_i, i \in E
667 // a_i^Tx >= b_i, i \in I
669 // init our initial x
670 double x
[fVariableCount
];
671 for (int i
= 0; i
< fVariableCount
; i
++)
675 // Note that the values of d and of G result from rewriting the
676 // ||x - desired|| we actually want to minimize.
677 double d
[fVariableCount
];
678 for (int i
= 0; i
< fVariableCount
; i
++)
682 ConstraintList
activeConstraints(constraintCount
);
684 for (int32 i
= 0; i
< constraintCount
; i
++) {
685 Constraint
* constraint
= fConstraints
->ItemAt(i
);
686 if (is_soft(constraint
))
688 double actualValue
= _ActualValue(constraint
, x
);
689 TRACE("constraint %ld: actual: %f constraint: %f\n", i
, actualValue
,
690 _RightSide(constraint
));
691 if (fuzzy_equals(actualValue
, _RightSide(constraint
)))
692 activeConstraints
.AddItem(constraint
);
695 // The main loop: Each iteration we try to get closer to the optimum
696 // solution. We compute a vector p that brings our x closer to the optimum.
697 // We do that by computing the QP resulting from our active constraint set,
698 // W^k. Afterward each iteration we adjust the active set.
699 TRACE_ONLY(int iteration
= 0;)
702 TRACE("\n[iteration %d]\n", iteration
++);
703 TRACE("active set:\n");
704 for (int32 i
= 0; i
< activeConstraints
.CountItems(); i
++) {
706 activeConstraints
.ItemAt(i
)->PrintToStream();
711 // min_p 1/2p^TGp + g_k^Tp
713 // with a_i \in activeConstraints
717 int32 activeCount
= activeConstraints
.CountItems();
718 if (activeCount
== 0) {
719 TRACE_ERROR("Solve(): Error: No more active constraints!\n");
723 // construct a matrix from the active constraints
724 int am
= activeCount
;
725 const int an
= fVariableCount
;
726 bool independentRows
[activeCount
];
727 zero_matrix(fActiveMatrix
, am
, an
);
729 for (int32 i
= 0; i
< activeCount
; i
++) {
730 Constraint
* constraint
= activeConstraints
.ItemAt(i
);
731 if (is_soft(constraint
))
733 SummandList
* summands
= constraint
->LeftSide();
734 for (int32 s
= 0; s
< summands
->CountItems(); s
++) {
735 Summand
* summand
= summands
->ItemAt(s
);
736 int32 variable
= summand
->Var()->Index();
737 if (constraint
->Op() == LinearProgramming::kLE
)
738 fActiveMatrix
[i
][variable
] = -summand
->Coeff();
740 fActiveMatrix
[i
][variable
] = summand
->Coeff();
744 // TODO: The fActiveMatrix is sparse (max 2 entries per row). There should be
745 // some room for optimization.
746 am
= remove_linearly_dependent_rows(fActiveMatrix
, fActiveMatrixTemp
,
747 independentRows
, am
, an
);
750 double gxd
[fVariableCount
];
751 multiply_matrix_vector(fG
, x
, fVariableCount
, fVariableCount
, gxd
);
752 add_vectors(gxd
, d
, fVariableCount
);
754 double p
[fVariableCount
];
755 if (!_SolveSubProblem(gxd
, am
, p
))
758 if (is_zero(p
, fVariableCount
)) {
759 // compute Lagrange multipliers lambda_i
760 // if lambda_i >= 0 for all i \in W^k \union inequality constraints,
762 // Otherwise remove the constraint with the smallest lambda_i
763 // from the active set.
764 // The derivation of the Lagrangian yields:
765 // \sum_{i \in W^k}(lambda_ia_i) = Gx_k + d
766 // Which is an system we can solve:
767 // A^Tlambda = Gx_k + d
769 // A^T is over-determined, hence we need to reduce the number of
770 // rows before we can solve it.
771 bool independentColumns
[an
];
772 double** aa
= fTemp1
;
773 transpose_matrix(fActiveMatrix
, aa
, am
, an
);
774 const int aam
= remove_linearly_dependent_rows(aa
, fTemp2
,
775 independentColumns
, an
, am
);
778 // This should not happen, since A has full row rank.
779 TRACE_ERROR("Solve(): Transposed A has less linear independent "
780 "rows than it has columns!\n");
784 // also reduce the number of rows on the right hand side
787 for (int i
= 0; i
< an
; i
++) {
788 if (independentColumns
[i
])
789 lambda
[index
++] = gxd
[i
];
792 bool success
= solve(aa
, aam
, lambda
);
794 // Impossible, since we've removed all linearly dependent rows.
795 TRACE_ERROR("Solve(): Failed to compute lambda!\n");
799 // find min lambda_i (only, if it's < 0, though)
800 double minLambda
= 0;
803 for (int i
= 0; i
< activeCount
; i
++) {
804 if (independentRows
[i
]) {
805 Constraint
* constraint
= activeConstraints
.ItemAt(i
);
806 if (constraint
->Op() != LinearProgramming::kEQ
) {
807 if (lambda
[index
] < minLambda
) {
808 minLambda
= lambda
[index
];
817 // if the min lambda is >= 0, we're done
818 if (minIndex
< 0 || fuzzy_equals(minLambda
, 0)) {
819 _SetResult(x
, values
);
823 // remove i from the active set
824 activeConstraints
.RemoveItemAt(minIndex
);
829 // if alpha_k < 1, add a barrier constraint to W^k
830 for (int32 i
= 0; i
< constraintCount
; i
++) {
831 Constraint
* constraint
= fConstraints
->ItemAt(i
);
832 if (activeConstraints
.HasItem(constraint
))
835 double divider
= _ActualValue(constraint
, p
);
836 if (divider
> 0 || fuzzy_equals(divider
, 0))
839 // (b_i - a_i^Tx_k) / a_i^Tp_k
840 double alphaI
= _RightSide(constraint
)
841 - _ActualValue(constraint
, x
);
843 if (alphaI
< alpha
) {
848 TRACE("alpha: %f, barrier: %d\n", alpha
, barrier
);
851 activeConstraints
.AddItem(fConstraints
->ItemAt(barrier
));
854 add_vectors_scaled(x
, p
, alpha
, fVariableCount
);
861 LayoutOptimizer::_SolveSubProblem(const double* d
, int am
, double* p
)
863 // We have to solve the QP subproblem:
864 // min_p 1/2p^TGp + d^Tp
866 // with a_i \in activeConstraints
868 // We use the null space method, i.e. we find matrices Y and Z, such that
869 // AZ = 0 and [Y Z] is regular. Then with
874 // (Z^TGZ)p_Z = -(Z^TYp_Y + Z^Tg) = -Z^Td
875 // which is a linear equation system, which we can solve.
877 const int an
= fVariableCount
;
879 // we get Y and Z by QR decomposition of A^T
881 double** const Q
= fQ
;
882 transpose_matrix(fActiveMatrix
, fTemp1
, am
, an
);
883 bool success
= qr_decomposition(fTemp1
, an
, am
, tempD
, Q
);
885 TRACE_ERROR("Solve(): QR decomposition failed!\n");
889 // Z is the (1, m + 1) minor of Q
891 const int zn
= an
- am
;
894 for (int i
= 0; i
< zm
; i
++)
897 // solve (Z^TGZ)p_Z = -Z^Td
900 transpose_matrix(Z
, fZtrans
, zm
, zn
);
903 multiply_matrix_vector(fZtrans
, d
, zn
, zm
, pz
);
904 negate_vector(pz
, zn
);
906 // fTemp2 = Ztrans * G * Z
907 multiply_matrices(fG
, Z
, fTemp1
, zm
, fVariableCount
, zn
);
908 multiply_matrices(fZtrans
, fTemp1
, fTemp2
, zn
, zm
, zn
);
910 success
= solve(fTemp2
, zn
, pz
);
912 TRACE_ERROR("Solve(): Failed to solve() system for p_Z\n");
917 multiply_matrix_vector(Z
, pz
, zm
, zn
, p
);
925 LayoutOptimizer::_SetResult(const double* x
, double* values
)
927 for (int i
= 0; i
< fVariableCount
; i
++)