#include "LayoutOptimizer.h"
#include <stdio.h>
#include <string.h>
#include <new>
#include <AutoDeleter.h>
#if TRACE_LAYOUT_OPTIMIZER
# define TRACE(format...) printf(format)
# define TRACE_ONLY(x) x
#else
# define TRACE(format...)
# define TRACE_ONLY(x)
#endif
#define TRACE_ERROR(format...) fprintf(stderr, format)
using std::nothrow;
static inline bool
is_zero(double* x, int n)
{
for (int i = 0; i < n; i++) {
if (!fuzzy_equals(x[i], 0))
return false;
}
return true;
}
static inline void
add_vectors(double* x, const double* y, int n)
{
for (int i = 0; i < n; i++)
x[i] += y[i];
}
static inline void
add_vectors_scaled(double* x, const double* y, double scalar, int n)
{
for (int i = 0; i < n; i++)
x[i] += y[i] * scalar;
}
static inline void
negate_vector(double* x, int n)
{
for (int i = 0; i < n; i++)
x[i] = -x[i];
}
static double**
allocate_matrix(int m, int n)
{
double** matrix = new(nothrow) double*[m];
if (!matrix)
return NULL;
double* values = new(nothrow) double[m * n];
if (!values) {
delete[] matrix;
return NULL;
}
double* row = values;
for (int i = 0; i < m; i++, row += n)
matrix[i] = row;
return matrix;
}
static void
free_matrix(double** matrix)
{
if (matrix) {
delete[] *matrix;
delete[] matrix;
}
}
static inline void
multiply_matrix_vector(const double* const* A, const double* x, int m, int n,
double* y)
{
for (int i = 0; i < m; i++) {
double sum = 0;
for (int k = 0; k < n; k++)
sum += A[i][k] * x[k];
y[i] = sum;
}
}
static void
multiply_matrices(const double* const* a, const double* const* b, double** c,
int m, int n, int l)
{
for (int i = 0; i < m; i++) {
for (int j = 0; j < l; j++) {
double sum = 0;
for (int k = 0; k < n; k++)
sum += a[i][k] * b[k][j];
c[i][j] = sum;
}
}
}
static inline void
transpose_matrix(const double* const* A, double** Atrans, int m, int n)
{
for (int i = 0; i < m; i++) {
for (int k = 0; k < n; k++)
Atrans[k][i] = A[i][k];
}
}
static inline void
zero_matrix(double** A, int m, int n)
{
for (int i = 0; i < m; i++) {
for (int k = 0; k < n; k++)
A[i][k] = 0;
}
}
static inline void
copy_matrix(const double* const* A, double** B, int m, int n)
{
for (int i = 0; i < m; i++) {
for (int k = 0; k < n; k++)
B[i][k] = A[i][k];
}
}
static inline void
multiply_optimization_matrix_vector(const double* x, int n, double* y)
{
if (n == 1) {
y[0] = x[0];
return;
}
y[0] = 2 * x[0] - x[1];
for (int i = 1; i < n - 1; i++)
y[i] = 2 * x[i] - x[i - 1] - x[i + 1];
y[n - 1] = x[n - 1] - x[n - 2];
}
static inline void
multiply_optimization_matrix_matrix(const double* const* A, int m, int n,
double** B)
{
if (m == 1) {
memcpy(B[0], A[0], n * sizeof(double));
return;
}
for (int k = 0; k < n; k++) {
B[0][k] = 2 * A[0][k] - A[1][k];
for (int i = 1; i < m - 1; i++)
B[i][k] = 2 * A[i][k] - A[i - 1][k] - A[i + 1][k];
B[m - 1][k] = A[m - 1][k] - A[m - 2][k];
}
}
template<typename Type>
static inline void
swap(Type& a, Type& b)
{
Type c = a;
a = b;
b = c;
}
bool
solve(double** a, int n, double* b)
{
int indices[n];
for (int i = 0; i < n; i++)
indices[i] = i;
for (int i = 0; i < n - 1; i++) {
int pivot = i;
double pivotValue = fabs(a[indices[i]][i]);
for (int j = i + 1; j < n; j++) {
int index = indices[j];
double value = fabs(a[index][i]);
if (value > pivotValue) {
pivot = j;
pivotValue = value;
}
}
if (fuzzy_equals(pivotValue, 0)) {
TRACE_ERROR("solve(): matrix is not regular\n");
return false;
}
if (pivot != i) {
swap(indices[i], indices[pivot]);
swap(b[i], b[pivot]);
}
pivot = indices[i];
for (int j = i + 1; j < n; j++) {
int index = indices[j];
double q = -a[index][i] / a[pivot][i];
a[index][i] = 0;
for (int k = i + 1; k < n; k++)
a[index][k] += a[pivot][k] * q;
b[j] += b[i] * q;
}
}
for (int i = n - 1; i >= 0; i--) {
int index = indices[i];
double sum = b[i];
for (int j = i + 1; j < n; j++)
sum -= a[index][j] * b[j];
b[i] = sum / a[index][i];
}
return true;
}
int
compute_dependencies(double** a, int m, int n, bool* independent)
{
int indices[m];
for (int i = 0; i < m; i++)
indices[i] = i;
int iterations = (m > n ? n : m);
int i = 0;
int column = 0;
for (; i < iterations && column < n; i++) {
int pivot = i;
do {
double pivotValue = fabs(a[indices[i]][column]);
for (int j = i + 1; j < m; j++) {
int index = indices[j];
double value = fabs(a[index][column]);
if (value > pivotValue) {
pivot = j;
pivotValue = value;
}
}
if (!fuzzy_equals(pivotValue, 0))
break;
column++;
} while (column < n);
if (column == n)
break;
if (pivot != i)
swap(indices[i], indices[pivot]);
pivot = indices[i];
independent[pivot] = true;
for (int j = i + 1; j < m; j++) {
int index = indices[j];
double q = -a[index][column] / a[pivot][column];
a[index][column] = 0;
for (int k = column + 1; k < n; k++)
a[index][k] += a[pivot][k] * q;
}
column++;
}
for (int j = i; j < m; j++)
independent[indices[j]] = false;
return i;
}
static int
remove_linearly_dependent_rows(double** A, double** temp, bool* independentRows,
int m, int n)
{
copy_matrix(A, temp, m, n);
int count = compute_dependencies(temp, m, n, independentRows);
if (count == m)
return count;
int index = 0;
for (int i = 0; i < m; i++) {
if (independentRows[i]) {
if (index < i) {
for (int k = 0; k < n; k++)
A[index][k] = A[i][k];
}
index++;
}
}
return count;
}
bool
qr_decomposition(double** a, int m, int n, double* d, double** q)
{
if (m < n)
return false;
for (int j = 0; j < n; j++) {
double innerProductU = 0;
for (int i = j + 1; i < m; i++)
innerProductU = innerProductU + a[i][j] * a[i][j];
double innerProduct = innerProductU + a[j][j] * a[j][j];
if (fuzzy_equals(innerProduct, 0)) {
TRACE_ERROR("qr_decomposition(): 0 column %d\n", j);
return false;
}
double alpha = (a[j][j] < 0 ? sqrt(innerProduct) : -sqrt(innerProduct));
d[j] = alpha;
double beta = 1 / (alpha * a[j][j] - innerProduct);
a[j][j] -= alpha;
for (int k = j + 1; k < n; k++) {
double sum = 0;
for (int i = j; i < m; i++)
sum += a[i][j] * a[i][k];
sum *= beta;
for (int i = j; i < m; i++)
a[i][k] += a[i][j] * sum;
}
innerProductU += a[j][j] * a[j][j];
double beta2 = -2 / innerProductU;
if (j == 0) {
for (int k = 0; k < m; k++) {
for (int i = 0; i < m; i++)
q[k][i] = beta2 * a[k][0] * a[i][0];
q[k][k] += 1;
}
} else {
for (int k = 0; k < m; k++) {
double sum = 0;
for (int i = j; i < m; i++)
sum += q[k][i] * a[i][j];
sum *= beta2;
for (int i = j; i < m; i++)
q[k][i] += sum * a[i][j];
}
}
}
return true;
}
struct MatrixDelete {
inline void operator()(double** matrix)
{
free_matrix(matrix);
}
};
typedef BPrivate::AutoDeleter<double*, MatrixDelete> MatrixDeleter;
struct LayoutOptimizer::Constraint {
Constraint(int32 left, int32 right, double value, bool equality,
int32 index)
: left(left),
right(right),
value(value),
index(index),
equality(equality)
{
}
double ActualValue(double* values) const
{
double result = 0;
if (right >= 0)
result = values[right];
if (left >= 0)
result -= values[left];
return result;
}
void Print() const
{
TRACE("c[%2ld] - c[%2ld] %2s %4d\n", right, left,
(equality ? "=" : ">="), (int)value);
}
int32 left;
int32 right;
double value;
int32 index;
bool equality;
};
LayoutOptimizer::LayoutOptimizer(int32 variableCount)
: fVariableCount(variableCount),
fConstraints(),
fVariables(new (nothrow) double[variableCount])
{
fTemp1 = allocate_matrix(fVariableCount, fVariableCount);
fTemp2 = allocate_matrix(fVariableCount, fVariableCount);
fZtrans = allocate_matrix(fVariableCount, fVariableCount);
fQ = allocate_matrix(fVariableCount, fVariableCount);
}
LayoutOptimizer::~LayoutOptimizer()
{
free_matrix(fTemp1);
free_matrix(fTemp2);
free_matrix(fZtrans);
free_matrix(fQ);
delete[] fVariables;
for (int32 i = 0;
Constraint* constraint = (Constraint*)fConstraints.ItemAt(i);
i++) {
delete constraint;
}
}
status_t
LayoutOptimizer::InitCheck() const
{
if (!fVariables || !fTemp1 || !fTemp2 || !fZtrans || !fQ)
return B_NO_MEMORY;
return B_OK;
}
LayoutOptimizer*
LayoutOptimizer::Clone() const
{
LayoutOptimizer* clone = new(nothrow) LayoutOptimizer(fVariableCount);
ObjectDeleter<LayoutOptimizer> cloneDeleter(clone);
if (!clone || clone->InitCheck() != B_OK
|| !clone->AddConstraintsFrom(this)) {
return NULL;
}
return cloneDeleter.Detach();
}
bool
LayoutOptimizer::AddConstraint(int32 left, int32 right, double value,
bool equality)
{
Constraint* constraint = new(nothrow) Constraint(left, right, value,
equality, fConstraints.CountItems());
if (constraint == NULL)
return false;
if (!fConstraints.AddItem(constraint)) {
delete constraint;
return false;
}
return true;
}
bool
LayoutOptimizer::AddConstraintsFrom(const LayoutOptimizer* other)
{
if (!other || other->fVariableCount != fVariableCount)
return false;
int32 count = fConstraints.CountItems();
for (int32 i = 0; i < count; i++) {
Constraint* constraint = (Constraint*)other->fConstraints.ItemAt(i);
if (!AddConstraint(constraint->left, constraint->right,
constraint->value, constraint->equality)) {
return false;
}
}
return true;
}
void
LayoutOptimizer::RemoveAllConstraints()
{
int32 count = fConstraints.CountItems();
for (int32 i = 0; i < count; i++) {
Constraint* constraint = (Constraint*)fConstraints.ItemAt(i);
delete constraint;
}
fConstraints.MakeEmpty();
}
bool
LayoutOptimizer::Solve(const double* desired, double size, double* values)
{
if (fVariables == NULL || desired == NULL|| values == NULL)
return false;
int32 constraintCount = fConstraints.CountItems() + 1;
fActiveMatrix = allocate_matrix(constraintCount, fVariableCount);
fActiveMatrixTemp = allocate_matrix(constraintCount, fVariableCount);
MatrixDeleter _(fActiveMatrix);
MatrixDeleter _2(fActiveMatrixTemp);
if (!fActiveMatrix || !fActiveMatrixTemp)
return false;
if (!AddConstraint(-1, fVariableCount - 1, size, true))
return false;
bool success = _Solve(desired, values);
Constraint* constraint = (Constraint*)fConstraints.RemoveItem(
constraintCount - 1);
delete constraint;
return success;
}
bool
LayoutOptimizer::_Solve(const double* desired, double* values)
{
int32 constraintCount = fConstraints.CountItems();
TRACE_ONLY(
TRACE("constraints:\n");
for (int32 i = 0; i < constraintCount; i++) {
TRACE(" %-2ld: ", i);
((Constraint*)fConstraints.ItemAt(i))->Print();
}
)
double x[fVariableCount];
x[0] = values[0];
for (int i = 1; i < fVariableCount; i++)
x[i] = values[i] + x[i - 1];
double d[fVariableCount];
for (int i = 0; i < fVariableCount - 1; i++)
d[i] = desired[i + 1] - desired[i];
d[fVariableCount - 1] = -desired[fVariableCount - 1];
BList activeConstraints(constraintCount);
for (int32 i = 0; i < constraintCount; i++) {
Constraint* constraint = (Constraint*)fConstraints.ItemAt(i);
double actualValue = constraint->ActualValue(x);
TRACE("constraint %ld: actual: %f constraint: %f\n", i, actualValue,
constraint->value);
if (fuzzy_equals(actualValue, constraint->value))
activeConstraints.AddItem(constraint);
}
TRACE_ONLY(int iteration = 0;)
while (true) {
TRACE_ONLY(
TRACE("\n[iteration %d]\n", iteration++);
TRACE("active set:\n");
for (int32 i = 0; i < activeConstraints.CountItems(); i++) {
TRACE(" ");
((Constraint*)activeConstraints.ItemAt(i))->Print();
}
)
int32 activeCount = activeConstraints.CountItems();
if (activeCount == 0) {
TRACE_ERROR("Solve(): Error: No more active constraints!\n");
return false;
}
int am = activeCount;
const int an = fVariableCount;
bool independentRows[activeCount];
zero_matrix(fActiveMatrix, am, an);
for (int32 i = 0; i < activeCount; i++) {
Constraint* constraint = (Constraint*)activeConstraints.ItemAt(i);
if (constraint->right >= 0)
fActiveMatrix[i][constraint->right] = 1;
if (constraint->left >= 0)
fActiveMatrix[i][constraint->left] = -1;
}
am = remove_linearly_dependent_rows(fActiveMatrix, fActiveMatrixTemp,
independentRows, am, an);
double gxd[fVariableCount];
multiply_optimization_matrix_vector(x, fVariableCount, gxd);
add_vectors(gxd, d, fVariableCount);
double p[fVariableCount];
if (!_SolveSubProblem(gxd, am, p))
return false;
if (is_zero(p, fVariableCount)) {
bool independentColumns[an];
double** aa = fTemp1;
transpose_matrix(fActiveMatrix, aa, am, an);
const int aam = remove_linearly_dependent_rows(aa, fTemp2,
independentColumns, an, am);
const int aan = am;
if (aam != aan) {
TRACE_ERROR("Solve(): Transposed A has less linear independent "
"rows than it has columns!\n");
return false;
}
double lambda[aam];
int index = 0;
for (int i = 0; i < an; i++) {
if (independentColumns[i])
lambda[index++] = gxd[i];
}
bool success = solve(aa, aam, lambda);
if (!success) {
TRACE_ERROR("Solve(): Failed to compute lambda!\n");
return false;
}
double minLambda = 0;
int minIndex = -1;
index = 0;
for (int i = 0; i < activeCount; i++) {
if (independentRows[i]) {
Constraint* constraint
= (Constraint*)activeConstraints.ItemAt(i);
if (!constraint->equality) {
if (lambda[index] < minLambda) {
minLambda = lambda[index];
minIndex = i;
}
}
index++;
}
}
if (minIndex < 0 || fuzzy_equals(minLambda, 0)) {
_SetResult(x, values);
return true;
}
activeConstraints.RemoveItem(minIndex);
} else {
double alpha = 1;
int barrier = -1;
for (int32 i = 0; i < constraintCount; i++) {
Constraint* constraint = (Constraint*)fConstraints.ItemAt(i);
if (activeConstraints.HasItem(constraint))
continue;
double divider = constraint->ActualValue(p);
if (divider > 0 || fuzzy_equals(divider, 0))
continue;
double alphaI = constraint->value
- constraint->ActualValue(x);
alphaI /= divider;
if (alphaI < alpha) {
alpha = alphaI;
barrier = i;
}
}
TRACE("alpha: %f, barrier: %d\n", alpha, barrier);
if (alpha < 1)
activeConstraints.AddItem(fConstraints.ItemAt(barrier));
add_vectors_scaled(x, p, alpha, fVariableCount);
}
}
}
bool
LayoutOptimizer::_SolveSubProblem(const double* d, int am, double* p)
{
const int an = fVariableCount;
double tempD[am];
double** const Q = fQ;
transpose_matrix(fActiveMatrix, fTemp1, am, an);
bool success = qr_decomposition(fTemp1, an, am, tempD, Q);
if (!success) {
TRACE_ERROR("Solve(): QR decomposition failed!\n");
return false;
}
const int zm = an;
const int zn = an - am;
double* Z[zm];
for (int i = 0; i < zm; i++)
Z[i] = Q[i] + am;
transpose_matrix(Z, fZtrans, zm, zn);
double pz[zm];
multiply_matrix_vector(fZtrans, d, zn, zm, pz);
negate_vector(pz, zn);
multiply_optimization_matrix_matrix(Z, an, zn, fTemp1);
multiply_matrices(fZtrans, fTemp1, fTemp2, zn, zm, zn);
success = solve(fTemp2, zn, pz);
if (!success) {
TRACE_ERROR("Solve(): Failed to solve() system for p_Z\n");
return false;
}
multiply_matrix_vector(Z, pz, zm, zn, p);
return true;
}
void
LayoutOptimizer::_SetResult(const double* x, double* values)
{
values[0] = x[0];
for (int i = 1; i < fVariableCount; i++)
values[i] = x[i] - x[i - 1];
}