summaryrefslogtreecommitdiffstats
path: root/src/syseq.h
blob: 3bd09f1068351b02b082e7e5d0556963049f4ed1 (plain)
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
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef SEEN_SYSEQ_H
#define SEEN_SYSEQ_H

/*
 * Auxiliary routines to solve systems of linear equations in several variants and sizes.
 *
 * Authors:
 *   Maximilian Albert <Anhalter42@gmx.de>
 *
 * Copyright (C) 2007  Authors
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <algorithm>
#include <iostream>
#include <iomanip>
#include <vector>
#include <cmath>

namespace SysEq {

enum SolutionKind {
    unique = 0,
    ambiguous,
    no_solution,
    solution_exists // FIXME: remove this; does not yield enough information
};

inline void explain(SolutionKind sol) {
    switch (sol) {
        case SysEq::unique:
            std::cout << "unique" << std::endl;
            break;
        case SysEq::ambiguous:
            std::cout << "ambiguous" << std::endl;
            break;
        case SysEq::no_solution:
            std::cout << "no solution" << std::endl;
            break;
        case SysEq::solution_exists:
            std::cout << "solution exists" << std::endl;
            break;
    }
}

inline double
determinant3x3 (double A[3][3]) {
    return (A[0][0]*A[1][1]*A[2][2] +
            A[0][1]*A[1][2]*A[2][0] +
            A[0][2]*A[1][0]*A[2][1] -
            A[0][0]*A[1][2]*A[2][1] -
            A[0][1]*A[1][0]*A[2][2] -
            A[0][2]*A[1][1]*A[2][0]);
}

/* Determinant of the 3x3 matrix having a, b, and c as columns */
inline double
determinant3v (const double a[3], const double b[3], const double c[3]) {
    return (a[0]*b[1]*c[2] +
            a[1]*b[2]*c[0] +
            a[2]*b[0]*c[1] -
            a[0]*b[2]*c[1] -
            a[1]*b[0]*c[2] -
            a[2]*b[1]*c[0]);
}

/* Copy the elements of A into B */
template <int S, int T>
inline void copy_mat(double A[S][T], double B[S][T]) {
    for (int i = 0; i < S; ++i) {
        for (int j = 0; j < T; ++j) {
            B[i][j] = A[i][j];
        }
    }
}

template <int S, int T>
inline void print_mat (const double A[S][T]) {
    std::cout.setf(std::ios::left, std::ios::internal);
    for (int i = 0; i < S; ++i) {
        for (int j = 0; j < T; ++j) {
            printf ("%8.2f ", A[i][j]);
        }
        std::cout << std::endl;;
    }    
}

/* Multiplication of two matrices */
template <int S, int U, int T>
inline void multiply(double A[S][U], double B[U][T], double res[S][T]) {
    for (int i = 0; i < S; ++i) {
        for (int j = 0; j < T; ++j) {
            double sum = 0;
            for (int k = 0; k < U; ++k) {
                sum += A[i][k] * B[k][j];
            }
            res[i][j] = sum;
        }
    }
}

/*
 * Multiplication of a matrix with a vector (for convenience, because with the previous
 * multiplication function we would always have to write v[i][0] for elements of the vector.
 */
template <int S, int T>
inline void multiply(double A[S][T], double v[T], double res[S]) {
    for (int i = 0; i < S; ++i) {
        double sum = 0;
        for (int k = 0; k < T; ++k) {
            sum += A[i][k] * v[k];
        }
        res[i] = sum;
    }
}

// Remark: Since we are using templates, we cannot separate declarations from definitions (which would
//         result in linker errors but have to include the definitions here for the following functions.
// FIXME: Maybe we should rework all this by using vector<vector<double> > structures for matrices
//        instead of double[S][T]. This would allow us to avoid templates. Would the performance degrade?

/*
 * Find the element of maximal absolute value in row i that 
 * does not lie in one of the columns given in avoid_cols.
 */
template <int S, int T>
static int find_pivot(const double A[S][T], unsigned int i, std::vector<int> const &avoid_cols) {
    if (i >= S) {
        return -1;
    }
    int pos = -1;
    double max = 0;
    for (int j = 0; j < T; ++j) {
        if (std::find(avoid_cols.begin(), avoid_cols.end(), j) != avoid_cols.end()) {
            continue; // skip "forbidden" columns
        }
        if (fabs(A[i][j]) > max) {
            pos = j;
            max = fabs(A[i][j]);
        }
    }
    return pos;
}

/*
 * Performs a single 'exchange step' in the Gauss-Jordan algorithm (i.e., swapping variables in the
 * two vectors).
 */
template <int S, int T>
static void gauss_jordan_step (double A[S][T], int row, int col) {
    double piv = A[row][col]; // pivot element
    /* adapt the entries of the matrix, first outside the pivot row/column */
    for (int k = 0; k < S; ++k) {
        if (k == row) continue;
        for (int l = 0; l < T; ++l) {
            if (l == col) continue;
            A[k][l] -= A[k][col] * A[row][l] / piv;
        }
    }
    /* now adapt the pivot column ... */
    for (int k = 0; k < S; ++k) {
        if (k == row) continue;
        A[k][col]  /= piv;
    }
    /* and the pivot row */
    for (int l = 0; l < T; ++l) {
        if (l == col) continue;
        A[row][l]  /= -piv;
    }
    /* finally, set the element at the pivot position itself */
    A[row][col] = 1/piv;
}

/*
 * Perform Gauss-Jordan elimination on the matrix A, optionally avoiding a given column during pivot search
 */
template <int S, int T>
static std::vector<int> gauss_jordan (double A[S][T], int avoid_col = -1) {
    std::vector<int> cols_used;
    if (avoid_col != -1) {
        cols_used.push_back (avoid_col);
    }
    for (int i = 0; i < S; ++i) {
        /* for each row find a pivot element of maximal absolute value, skipping the columns that were used before */
        int col = find_pivot<S,T>(A, i, cols_used);
        cols_used.push_back(col);
        if (col == -1) {
            // no non-zero elements in the row
            return cols_used;
        }

        /* if pivot search was successful we can perform a Gauss-Jordan step */
        gauss_jordan_step<S,T> (A, i, col);
    }
    if (avoid_col != -1) {
        // since the columns that were used will be needed later on, we need to clean up the column vector
        cols_used.erase(cols_used.begin());
    }
    return cols_used;
}

/* compute the modified value that x[index] needs to assume so that in the end we have x[index]/x[T-1] = val */
template <int S, int T>
static double projectify (std::vector<int> const &cols, const double B[S][T], const double x[T],
                          const int index, const double val) {
    double val_proj = 0.0;
    if (index != -1) {
        int c = -1;
        for (int i = 0; i < S; ++i) {
            if (cols[i] == T-1) {
                c = i;
                break;
            }
        }
        if (c == -1) {
            std::cout << "Something is wrong. Rethink!!" << std::endl;
            return SysEq::no_solution;
        }

        double sp = 0;
        for (int j = 0; j < T; ++j) {
            if (j == index) continue;
            sp += B[c][j] * x[j];
        }
        double mu = 1 - val * B[c][index];
        if (fabs(mu) < 1E-6) {
            std::cout << "No solution since adapted value is too close to zero" << std::endl;
            return SysEq::no_solution;
        }
        val_proj = sp*val/mu;
    } else {
        val_proj = val; // FIXME: Is this correct?
    }
    return val_proj;
}

/**
 * Solve the linear system of equations \a A * \a x = \a v where we additionally stipulate
 * \a x[\a index] = \a val if \a index is not -1. The system is solved using Gauss-Jordan
 * elimination so that we can gracefully handle the case that zero or infinitely many
 * solutions exist.
 *
 * Since our application will be to finding preimages of projective mappings, we provide
 * an additional argument \a proj. If this is true, we find a solution of
 * \a x[\a index]/\a x[\T - 1] = \a val instead (i.e., we want the corresponding coordinate
 * of the _affine image_ of the point with homogeneous coordinate vector \a x to be equal
 * to \a val.
 *
 * Remark: We don't need this but it would be relatively simple to let the calling function
 * prescripe the value of _multiple_ components of the solution vector instead of only a single one.
 */
template <int S, int T> SolutionKind gaussjord_solve (double A[S][T], double x[T], double v[S],
                                                      int index = -1, double val = 0.0, bool proj = false) {
    double B[S][T];
    //copy_mat<S,T>(A,B);
    SysEq::copy_mat<S,T>(A,B);
    std::vector<int> cols = gauss_jordan<S,T>(B, index);
    if (std::find(cols.begin(), cols.end(), -1) != cols.end()) {
        // pivot search failed for some row so the system is not solvable
        return SysEq::no_solution;
    }

    /* the vector x is filled with the coefficients of the desired solution vector at appropriate places;
     * the other components are set to zero, and we additionally set x[index] = val if applicable
     */
    std::vector<int>::iterator k;
    for (int j = 0; j < S; ++j) {
        x[cols[j]] = v[j];
    }
    for (int j = 0; j < T; ++j) {
        k = std::find(cols.begin(), cols.end(), j);
        if (k == cols.end()) {
            x[j] = 0;
        }
    }

    // we need to adapt the value if we are in the "projective case" (see above)
    double val_new = (proj ? projectify<S,T>(cols, B, x, index, val) : val);

    if (index >= 0 && index < T) {
        // we want the specified coefficient of the solution vector to have a given value
        x[index] = val_new;
    }

    /* the final solution vector is now obtained as the product B*x, where B is the matrix
     * obtained by Gauss-Jordan manipulation of A; we use w as an auxiliary vector and
     * afterwards copy the result back to x
     */
    double w[S];
    SysEq::multiply<S,T>(B,x,w); // initializes w
    for (int j = 0; j < S; ++j) {
        x[cols[j]] = w[j];
    }

    if (S + (index == -1 ? 0 : 1) == T) {
        return SysEq::unique;
    } else {
        return SysEq::ambiguous;
    }
}

} // namespace SysEq

#endif /* __SYSEQ_H__ */

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :