Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
choleski.h
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2019, 2026 Aurélien PIERRE.
4 Copyright (C) 2019 Heiko Bauke.
5 Copyright (C) 2019 luzpaz.
6 Copyright (C) 2019-2020 Pascal Obry.
7 Copyright (C) 2020 Ralf Brown.
8 Copyright (C) 2022 Martin Bařinka.
9 Copyright (C) 2022 Sakari Kapanen.
10 Copyright (C) 2023 Luca Zulberti.
11
12 darktable is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
16
17 darktable is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with darktable. If not, see <http://www.gnu.org/licenses/>.
24*/
25
26#include <math.h>
27#include <stdlib.h>
28#include <stdio.h>
29#include <string.h>
30#include <time.h>
31
32#include "common/darktable.h"
33#include "common/imagebuf.h"
34#include "control/control.h" // dt_control_log (OOM error toast) — self-contained include order
36
37
38/* DOCUMENTATION
39 *
40 * Choleski decomposition is a fast way to solve linear systems of equations
41 * described by a positive definite hermitian (= square and symmetrical) matrix.
42 * It is a special case of LU decompositions enabling special optimizations.
43 * For matrices not matching this requirement,
44 * you need to use the Gauss-Jordan elimination in iop/gaussian_elemination.h,
45 * which is about twice as slow but more general.
46 *
47 * To solve A x = y, for x, with A a positive definite hermitian real matrix :
48 *
49 * 1) find L such that A = L x L' (Choleski decomposition)
50 * 2) Second step : solve L x b = y for b (triangular descent)
51 * 3) Third step : solve L' x x = b for x (triangular ascent)
52 *
53 * L is a lower diagonal matrix such that :
54 *
55 * [ l11 0 0 ]
56 * L = [ l12 l22 0 ] (if n = 3)
57 * [ l13 l23 l33 ]
58 *
59 * and L' is its transpose :
60 *
61 * [ l11 l12 l13 ]
62 * L' = [ 0 l22 l23 ] (if n = 3)
63 * [ 0 0 l33 ]
64 *
65 * We use the Cholesky-Banachiewicz algorithm for the decomposition because it
66 * operates row by row, which is more suitable with C memory layout.
67 *
68 * The return codes are in sync with /iop/gaussian_elimination.h, in order, maybe one day,
69 * to have a general linear solving lib that could for example iterate over methods until
70 * one succeed.
71 *
72 * We didn't bother to parallelize the code nor to make it use double floating point precision
73 * because it's already fast enough (2 to 45 ms for 16x16 matrix on Xeon) and used for properly
74 * conditioned matrices.
75 *
76 * Vectorization leads to slow-downs here since we access matrices row-wise and column-wise,
77 * in a non-contiguous fashion.
78 *
79 * References :
80 * "Analyse numérique pour ingénieurs", 4e edition, André Fortin,
81 * Presses Internationales de Polytechnique Montréal, 2011.
82 *
83 * Cholesky method,
84 * https://algowiki-project.org/en/Cholesky_method#The_.5Bmath.5DLL.5ET.5B.2Fmath.5D_decomposition
85 * https://en.wikipedia.org/wiki/Cholesky_decomposition
86 * https://rosettacode.org/wiki/Cholesky_decomposition#C
87 *
88 */
89
90
91static inline int choleski_decompose_fast(const float *const restrict A,
92 float *const restrict L, size_t n)
93{
94 // A is input nxn matrix, decompose it into L such that A = L x L'
95 // fast variant : we don't check values for negatives in sqrt,
96 // ensure you know the properties of your matrix.
97
98 if(A[0] <= 0.0f) return 0; // failure : non positive definite matrice
99
100 for(size_t i = 0; i < n; i++)
101 for(size_t j = 0; j < (i + 1); j++)
102 {
103 float sum = 0.0f;
104
105 for(size_t k = 0; k < j; k++)
106 sum += L[i * n + k] * L[j * n + k];
107
108 L[i * n + j] = (i == j) ?
109 sqrtf(A[i * n + i] - sum) :
110 (A[i * n + j] - sum) / L[j * n + j];
111 }
112
113 return 1; // success
114}
115
116
117static inline int choleski_decompose_safe(const float *const restrict A,
118 float *const restrict L, size_t n)
119{
120 // A is input nxn matrix, decompose it into L such that A = L x L'
121 // slow and safe variant : we check values for negatives in sqrt and divisions by 0.
122
123 if(A[0] <= 0.0f) return 0; // failure : non positive definite matrice
124
125 int valid = 1;
126
127 for(size_t i = 0; i < n; i++)
128 for(size_t j = 0; j < (i + 1); j++)
129 {
130 float sum = 0.0f;
131
132 for(size_t k = 0; k < j; k++)
133 sum += L[i * n + k] * L[j * n + k];
134
135 if(i == j)
136 {
137 const float temp = A[i * n + i] - sum;
138
139 if(temp < 0.0f)
140 {
141 valid = 0;
142 L[i * n + j] = NAN;
143 }
144 else
145 L[i * n + j] = sqrtf(A[i * n + i] - sum);
146 }
147 else
148 {
149 const float temp = L[j * n + j];
150
151 if(temp == 0.0f)
152 {
153 valid = 0;
154 L[i * n + j] = NAN;
155 }
156 else
157 L[i * n + j] = (A[i * n + j] - sum) / temp;
158 }
159 }
160
161 return valid; // success ?
162}
163
164
165static inline int triangular_descent_fast(const float *const restrict L,
166 const float *const restrict y, float *const restrict b,
167 const size_t n)
168{
169 // solve L x b = y for b
170 // use the lower triangular part of L from top to bottom
171
172 for(size_t i = 0; i < n; ++i)
173 {
174 float sum = y[i];
175 for(size_t j = 0; j < i; ++j)
176 sum -= L[i * n + j] * b[j];
177
178 b[i] = sum / L[i * n + i];
179 }
180
181 return 1; // success !
182}
183
184
185static inline int triangular_descent_safe(const float *const restrict L,
186 const float *const restrict y, float *const restrict b,
187 const size_t n)
188{
189 // solve L x b = y for b
190 // use the lower triangular part of L from top to bottom
191
192 int valid = 1;
193
194 for(size_t i = 0; i < n; ++i)
195 {
196 float sum = y[i];
197 for(size_t j = 0; j < i; ++j)
198 sum -= L[i * n + j] * b[j];
199
200 const float temp = L[i * n + i];
201
202 if(temp != 0.0f)
203 b[i] = sum / temp;
204 else
205 {
206 b[i] = NAN;
207 valid = 0;
208 }
209 }
210
211 return valid; // success ?
212}
213
214
215static inline int triangular_ascent_fast(const float *const restrict L,
216 const float *const restrict b, float *const restrict x,
217 const size_t n)
218{
219 // solve L' x x = b for x
220 // use the lower triangular part of L transposed from bottom to top
221
222 for(int i = (n - 1); i > -1 ; --i)
223 {
224 float sum = b[i];
225 for(int j = (n - 1); j > i; --j)
226 sum -= L[j * n + i] * x[j];
227
228 x[i] = sum / L[i * n + i];
229 }
230
231 return 1; // success !
232}
233
234
235static inline int triangular_ascent_safe(const float *const restrict L,
236 const float *const restrict b, float *const restrict x,
237 const size_t n)
238{
239 // solve L' x x = b for x
240 // use the lower triangular part of L transposed from bottom to top
241
242 int valid = 1;
243
244 for(int i = (n - 1); i > -1 ; --i)
245 {
246 float sum = b[i];
247 for(int j = (n - 1); j > i; --j)
248 sum -= L[j * n + i] * x[j];
249
250 const float temp = L[i * n + i];
251 if(temp != 0.0f)
252 x[i] = sum / temp;
253 else
254 {
255 x[i] = NAN;
256 valid = 0;
257 }
258 }
259
260 return valid; // success ?
261}
262
263
264static inline int solve_hermitian(const float *const restrict A,
265 float *const restrict y,
266 const size_t n, const int checks)
267{
268 // Solve A x = y where A an hermitian positive definite matrix n x n
269 // x and y are n vectors. Output the result in y
270
271 // A and y need to be 64-bits aligned, which is darktable's default memory alignment
272 // if you used DT_ALIGNED_ARRAY and dt_alloc_align_float(...) to declare arrays and pointers
273
274 // If you are sure about the properties of the matrix A (symmetrical square definite positive)
275 // because you built it yourself, set checks == FALSE to branch to the fast track that
276 // skips validity checks.
277
278 // If you are unsure about A, because it is user-set, set checks == TRUE to branch
279 // to the safe but slower path.
280
281 // clock_t start = clock();
282
283 int valid = 0;
284 int err = 0;
285 float *const restrict x = dt_alloc_align_float(n);
286 float *const restrict L = dt_alloc_align_float(n * n);
287
288 if(IS_NULL_PTR(x) || IS_NULL_PTR(L))
289 {
290 dt_control_log(_("Choleski decomposition failed to allocate memory, check your RAM settings"));
291 fprintf(stdout, "Choleski decomposition failed to allocate memory, check your RAM settings\n");
292 err = 1;
293 goto error;
294 }
295
296 // LU decomposition
297 valid = (checks) ? choleski_decompose_safe(A, L, n) :
299 if(!valid) fprintf(stdout, "Cholesky decomposition returned NaNs\n");
300
301 // Triangular descent
302 if(valid)
303 valid = (checks) ? triangular_descent_safe(L, y, x, n) :
305 if(!valid) fprintf(stdout, "Cholesky LU triangular descent returned NaNs\n");
306
307 // Triangular ascent
308 if(valid)
309 valid = (checks) ? triangular_ascent_safe(L, x, y, n) :
311 if(!valid) fprintf(stdout, "Cholesky LU triangular ascent returned NaNs\n");
312
313 if(!valid) err = 1;
314
315error:
318
319 //clock_t end = clock();
320 //fprintf(stdout, "hermitian matrix solving took : %f s\n", ((float) (end - start)) / CLOCKS_PER_SEC);
321
322 return err;
323}
324
325
326static inline int transpose_dot_matrix(float *const restrict A, // input
327 float *const restrict A_square, // output
328 const size_t m, const size_t n)
329{
330 // Construct the square symmetrical definite positive matrix A' A,
331 // BUT only compute the lower triangle part for performance
332
333 for(size_t i = 0; i < n; ++i)
334 for(size_t j = 0; j < (i + 1); ++j)
335 {
336 float sum = 0.0f;
337 for(size_t k = 0; k < m; ++k)
338 sum += A[k * n + i] * A[k * n + j];
339
340 A_square[i * n + j] = sum;
341 }
342
343 return 0;
344}
345
346
347static inline int transpose_dot_vector(float *const restrict A, // input
348 float *const restrict y, // input
349 float *const restrict y_square, // output
350 const size_t m, const size_t n)
351{
352 // Construct the vector A' y
353
354 for(size_t i = 0; i < n; ++i)
355 {
356 float sum = 0.0f;
357 for(size_t k = 0; k < m; ++k)
358 sum += A[k * n + i] * y[k];
359
360 y_square[i] = sum;
361 }
362
363 return 0;
364}
365
366
367static inline int pseudo_solve(float *const restrict A,
368 float *const restrict y,
369 const size_t m, const size_t n, const int checks)
370{
371 // Solve the linear problem A x = y with the over-constrained rectanguler matrice A
372 // of dimension m x n (m >= n) by the least squares method
373
374 //clock_t start = clock();
375
376 int err = 0;
377 if(m < n)
378 {
379 fprintf(stdout, "Pseudo solve: cannot cast %" G_GSIZE_FORMAT " \303\227 %" G_GSIZE_FORMAT " matrice\n", m, n);
380 return 1;
381 }
382
383 float *const restrict A_square = dt_alloc_align_float(n * n);
384 float *const restrict y_square = dt_alloc_align_float(n);
385
386 if(IS_NULL_PTR(A_square) || IS_NULL_PTR(y_square))
387 {
388 dt_control_log(_("Choleski decomposition failed to allocate memory, check your RAM settings"));
389 err = 1;
390 goto error;
391 }
392
393 #ifdef _OPENMP
394 #pragma omp parallel sections
395 #endif
396 {
397 #ifdef _OPENMP
398 #pragma omp section
399 #endif
400 {
401 // Prepare the least squares matrix = A' A
402 transpose_dot_matrix(A, A_square, m, n);
403 }
404
405 #ifdef _OPENMP
406 #pragma omp section
407 #endif
408 {
409 // Prepare the y square vector = A' y
410 transpose_dot_vector(A, y, y_square, m, n);
411 }
412 }
413
414
415 // Solve A' A x = A' y for x
416 if(solve_hermitian(A_square, y_square, n, checks))
417 {
418 err = 1;
419 goto error;
420 }
421 dt_simd_memcpy(y_square, y, n);
422
423error:
424 dt_free_align(y_square);
425 dt_free_align(A_square);
426
427 //clock_t end = clock();
428 //fprintf(stdout, "hermitian matrix solving took : %f s\n", ((float) (end - start)) / CLOCKS_PER_SEC);
429
430 return err;
431}
432
433
434// clang-format off
435// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
436// vim: shiftwidth=2 expandtab tabstop=2 cindent
437// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
438// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define m
Definition basecurve.c:278
static int transpose_dot_vector(float *const restrict A, float *const restrict y, float *const restrict y_square, const size_t m, const size_t n)
Definition choleski.h:347
static int transpose_dot_matrix(float *const restrict A, float *const restrict A_square, const size_t m, const size_t n)
Definition choleski.h:326
static int choleski_decompose_safe(const float *const restrict A, float *const restrict L, size_t n)
Definition choleski.h:117
static int solve_hermitian(const float *const restrict A, float *const restrict y, const size_t n, const int checks)
Definition choleski.h:264
static int pseudo_solve(float *const restrict A, float *const restrict y, const size_t m, const size_t n, const int checks)
Definition choleski.h:367
static int choleski_decompose_fast(const float *const restrict A, float *const restrict L, size_t n)
Definition choleski.h:91
static int triangular_ascent_safe(const float *const restrict L, const float *const restrict b, float *const restrict x, const size_t n)
Definition choleski.h:235
static int triangular_ascent_fast(const float *const restrict L, const float *const restrict b, float *const restrict x, const size_t n)
Definition choleski.h:215
static int triangular_descent_fast(const float *const restrict L, const float *const restrict y, float *const restrict b, const size_t n)
Definition choleski.h:165
static int triangular_descent_safe(const float *const restrict L, const float *const restrict y, float *const restrict b, const size_t n)
Definition choleski.h:185
#define A(y, x)
void dt_control_log(const char *msg,...)
Definition control.c:777
#define dt_free_align(ptr)
Definition darktable.h:503
static float * dt_alloc_align_float(size_t pixels)
Definition darktable.h:516
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
static __DT_CLONE_TARGETS__ void dt_simd_memcpy(const float *const __restrict__ in, float *const __restrict__ out, const size_t num_elem)
Definition imagebuf.h:68
static const float x
float *const restrict const size_t k