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#ifndef DT_MATH_CHOLESKI_H
27#define DT_MATH_CHOLESKI_H
28
29#include <glib.h>
30#include <glib/gi18n.h>
31#include <math.h>
32#include <stdlib.h>
33#include <stdio.h>
34#include <string.h>
35#include <time.h>
36
37#include "common/imagebuf.h"
38#include "system/macros.h"
39#include "system/mem_alloc.h"
40#include "system/openmp.h" // dt_omp_in_parallel(): never log a per-thread solver failure
41#include "common/logging.h"
42
43
44/* DOCUMENTATION
45 *
46 * Choleski decomposition is a fast way to solve linear systems of equations
47 * described by a positive definite hermitian (= square and symmetrical) matrix.
48 * It is a special case of LU decompositions enabling special optimizations.
49 * For matrices not matching this requirement,
50 * you need to use the Gauss-Jordan elimination in iop/gaussian_elemination.h,
51 * which is about twice as slow but more general.
52 *
53 * To solve A x = y, for x, with A a positive definite hermitian real matrix :
54 *
55 * 1) find L such that A = L x L' (Choleski decomposition)
56 * 2) Second step : solve L x b = y for b (triangular descent)
57 * 3) Third step : solve L' x x = b for x (triangular ascent)
58 *
59 * L is a lower diagonal matrix such that :
60 *
61 * [ l11 0 0 ]
62 * L = [ l12 l22 0 ] (if n = 3)
63 * [ l13 l23 l33 ]
64 *
65 * and L' is its transpose :
66 *
67 * [ l11 l12 l13 ]
68 * L' = [ 0 l22 l23 ] (if n = 3)
69 * [ 0 0 l33 ]
70 *
71 * We use the Cholesky-Banachiewicz algorithm for the decomposition because it
72 * operates row by row, which is more suitable with C memory layout.
73 *
74 * The return codes are in sync with /iop/gaussian_elimination.h, in order, maybe one day,
75 * to have a general linear solving lib that could for example iterate over methods until
76 * one succeed.
77 *
78 * We didn't bother to parallelize the code nor to make it use double floating point precision
79 * because it's already fast enough (2 to 45 ms for 16x16 matrix on Xeon) and used for properly
80 * conditioned matrices.
81 *
82 * Vectorization leads to slow-downs here since we access matrices row-wise and column-wise,
83 * in a non-contiguous fashion.
84 *
85 * References :
86 * "Analyse numérique pour ingénieurs", 4e edition, André Fortin,
87 * Presses Internationales de Polytechnique Montréal, 2011.
88 *
89 * Cholesky method,
90 * https://algowiki-project.org/en/Cholesky_method#The_.5Bmath.5DLL.5ET.5B.2Fmath.5D_decomposition
91 * https://en.wikipedia.org/wiki/Cholesky_decomposition
92 * https://rosettacode.org/wiki/Cholesky_decomposition#C
93 *
94 */
95
96
97static inline int choleski_decompose_fast(const float *const restrict A,
98 float *const restrict L, size_t n)
99{
100 // A is input nxn matrix, decompose it into L such that A = L x L'
101 // fast variant : we don't check values for negatives in sqrt,
102 // ensure you know the properties of your matrix.
103
104 if(A[0] <= 0.0f) return 0; // failure : non positive definite matrice
105
106 for(size_t i = 0; i < n; i++)
107 for(size_t j = 0; j < (i + 1); j++)
108 {
109 float sum = 0.0f;
110
111 for(size_t k = 0; k < j; k++)
112 sum += L[i * n + k] * L[j * n + k];
113
114 L[i * n + j] = (i == j) ?
115 sqrtf(A[i * n + i] - sum) :
116 (A[i * n + j] - sum) / L[j * n + j];
117 }
118
119 return 1; // success
120}
121
122
123static inline int choleski_decompose_safe(const float *const restrict A,
124 float *const restrict L, size_t n)
125{
126 // A is input nxn matrix, decompose it into L such that A = L x L'
127 // slow and safe variant : we check values for negatives in sqrt and divisions by 0.
128
129 if(A[0] <= 0.0f) return 0; // failure : non positive definite matrice
130
131 int valid = 1;
132
133 for(size_t i = 0; i < n; i++)
134 for(size_t j = 0; j < (i + 1); j++)
135 {
136 float sum = 0.0f;
137
138 for(size_t k = 0; k < j; k++)
139 sum += L[i * n + k] * L[j * n + k];
140
141 if(i == j)
142 {
143 const float temp = A[i * n + i] - sum;
144
145 if(temp < 0.0f)
146 {
147 valid = 0;
148 L[i * n + j] = NAN;
149 }
150 else
151 L[i * n + j] = sqrtf(A[i * n + i] - sum);
152 }
153 else
154 {
155 const float temp = L[j * n + j];
156
157 if(temp == 0.0f)
158 {
159 valid = 0;
160 L[i * n + j] = NAN;
161 }
162 else
163 L[i * n + j] = (A[i * n + j] - sum) / temp;
164 }
165 }
166
167 return valid; // success ?
168}
169
170
171static inline int triangular_descent_fast(const float *const restrict L,
172 const float *const restrict y, float *const restrict b,
173 const size_t n)
174{
175 // solve L x b = y for b
176 // use the lower triangular part of L from top to bottom
177
178 for(size_t i = 0; i < n; ++i)
179 {
180 float sum = y[i];
181 for(size_t j = 0; j < i; ++j)
182 sum -= L[i * n + j] * b[j];
183
184 b[i] = sum / L[i * n + i];
185 }
186
187 return 1; // success !
188}
189
190
191static inline int triangular_descent_safe(const float *const restrict L,
192 const float *const restrict y, float *const restrict b,
193 const size_t n)
194{
195 // solve L x b = y for b
196 // use the lower triangular part of L from top to bottom
197
198 int valid = 1;
199
200 for(size_t i = 0; i < n; ++i)
201 {
202 float sum = y[i];
203 for(size_t j = 0; j < i; ++j)
204 sum -= L[i * n + j] * b[j];
205
206 const float temp = L[i * n + i];
207
208 if(temp != 0.0f)
209 b[i] = sum / temp;
210 else
211 {
212 b[i] = NAN;
213 valid = 0;
214 }
215 }
216
217 return valid; // success ?
218}
219
220
221static inline int triangular_ascent_fast(const float *const restrict L,
222 const float *const restrict b, float *const restrict x,
223 const size_t n)
224{
225 // solve L' x x = b for x
226 // use the lower triangular part of L transposed from bottom to top
227
228 for(int i = (n - 1); i > -1 ; --i)
229 {
230 float sum = b[i];
231 for(int j = (n - 1); j > i; --j)
232 sum -= L[j * n + i] * x[j];
233
234 x[i] = sum / L[i * n + i];
235 }
236
237 return 1; // success !
238}
239
240
241static inline int triangular_ascent_safe(const float *const restrict L,
242 const float *const restrict b, float *const restrict x,
243 const size_t n)
244{
245 // solve L' x x = b for x
246 // use the lower triangular part of L transposed from bottom to top
247
248 int valid = 1;
249
250 for(int i = (n - 1); i > -1 ; --i)
251 {
252 float sum = b[i];
253 for(int j = (n - 1); j > i; --j)
254 sum -= L[j * n + i] * x[j];
255
256 const float temp = L[i * n + i];
257 if(temp != 0.0f)
258 x[i] = sum / temp;
259 else
260 {
261 x[i] = NAN;
262 valid = 0;
263 }
264 }
265
266 return valid; // success ?
267}
268
269
270static inline int solve_hermitian(const float *const restrict A,
271 float *const restrict y,
272 const size_t n, const int checks)
273{
274 // Solve A x = y where A an hermitian positive definite matrix n x n
275 // x and y are n vectors. Output the result in y
276
277 // A and y need to be 64-bits aligned, which is darktable's default memory alignment
278 // if you used DT_ALIGNED_ARRAY and dt_alloc_align_float(...) to declare arrays and pointers
279
280 // If you are sure about the properties of the matrix A (symmetrical square definite positive)
281 // because you built it yourself, set checks == FALSE to branch to the fast track that
282 // skips validity checks.
283
284 // If you are unsure about A, because it is user-set, set checks == TRUE to branch
285 // to the safe but slower path.
286
287 // clock_t start = clock();
288
289 int valid = 0;
290 int err = 0;
291 float *const restrict x = dt_alloc_align_float(n);
292 float *const restrict L = dt_alloc_align_float(n * n);
293
294 if(IS_NULL_PTR(x) || IS_NULL_PTR(L))
295 {
296 dt_print(DT_DEBUG_ALWAYS, "[choleski] out of memory allocating the %" G_GSIZE_FORMAT
297 " x %" G_GSIZE_FORMAT " decomposition\n", n, n);
298 err = 1;
299 goto error;
300 }
301
302 // Which stage first reported NaNs, or NULL while the solve is still valid. Only the FIRST
303 // failure is reported: the three stages are chained on `valid`, so once the decomposition
304 // fails the descent and ascent are never even run -- printing a line per stage regardless
305 // turned every single failure into three messages.
306 const char *failed_stage = NULL;
307
308 // LU decomposition
309 valid = (checks) ? choleski_decompose_safe(A, L, n) :
311 if(!valid) failed_stage = "decomposition";
312
313 // Triangular descent
314 if(valid)
315 {
316 valid = (checks) ? triangular_descent_safe(L, y, x, n) :
318 if(!valid) failed_stage = "LU triangular descent";
319 }
320
321 // Triangular ascent
322 if(valid)
323 {
324 valid = (checks) ? triangular_ascent_safe(L, x, y, n) :
326 if(!valid) failed_stage = "LU triangular ascent";
327 }
328
329 if(!valid)
330 {
331 err = 1;
332 // A non-SPD matrix is the caller's problem to handle -- every caller here checks the return
333 // code and has a fallback -- so this is a diagnostic, not an error report, and it must not
334 // be emitted from inside a parallel loop: one message per thread per pixel-loop iteration
335 // both floods the log and interleaves mid-line with the other threads' output (which is how
336 // issue #1094 reported "- - -" and torn lines). Callers that solve in a loop are expected to
337 // aggregate their own failure count and report it once, outside the parallel region.
338 if(!dt_omp_in_parallel())
339 dt_print(DT_DEBUG_ALWAYS, "[choleski] %s returned NaNs on the %" G_GSIZE_FORMAT
340 " x %" G_GSIZE_FORMAT " matrix: not positive-definite\n",
341 failed_stage, n, n);
342 }
343
344error:
347
348 //clock_t end = clock();
349 //fprintf(stdout, "hermitian matrix solving took : %f s\n", ((float) (end - start)) / CLOCKS_PER_SEC);
350
351 return err;
352}
353
354
355static inline int transpose_dot_matrix(float *const restrict A, // input
356 float *const restrict A_square, // output
357 const size_t m, const size_t n)
358{
359 // Construct the square symmetrical definite positive matrix A' A,
360 // BUT only compute the lower triangle part for performance
361
362 for(size_t i = 0; i < n; ++i)
363 for(size_t j = 0; j < (i + 1); ++j)
364 {
365 float sum = 0.0f;
366 for(size_t k = 0; k < m; ++k)
367 sum += A[k * n + i] * A[k * n + j];
368
369 A_square[i * n + j] = sum;
370 }
371
372 return 0;
373}
374
375
376static inline int transpose_dot_vector(float *const restrict A, // input
377 float *const restrict y, // input
378 float *const restrict y_square, // output
379 const size_t m, const size_t n)
380{
381 // Construct the vector A' y
382
383 for(size_t i = 0; i < n; ++i)
384 {
385 float sum = 0.0f;
386 for(size_t k = 0; k < m; ++k)
387 sum += A[k * n + i] * y[k];
388
389 y_square[i] = sum;
390 }
391
392 return 0;
393}
394
395
396static inline int pseudo_solve(float *const restrict A,
397 float *const restrict y,
398 const size_t m, const size_t n, const int checks)
399{
400 // Solve the linear problem A x = y with the over-constrained rectanguler matrice A
401 // of dimension m x n (m >= n) by the least squares method
402
403 //clock_t start = clock();
404
405 int err = 0;
406 if(m < n)
407 {
408 fprintf(stdout, "Pseudo solve: cannot cast %" G_GSIZE_FORMAT " \303\227 %" G_GSIZE_FORMAT " matrice\n", m, n);
409 return 1;
410 }
411
412 float *const restrict A_square = dt_alloc_align_float(n * n);
413 float *const restrict y_square = dt_alloc_align_float(n);
414
415 if(IS_NULL_PTR(A_square) || IS_NULL_PTR(y_square))
416 {
417 dt_print(DT_DEBUG_ALWAYS, "[choleski] out of memory allocating the %" G_GSIZE_FORMAT
418 " x %" G_GSIZE_FORMAT " pseudo-solve\n", n, n);
419 err = 1;
420 goto error;
421 }
422
423 #ifdef _OPENMP
424 #pragma omp parallel sections
425 #endif
426 {
427 #ifdef _OPENMP
428 #pragma omp section
429 #endif
430 {
431 // Prepare the least squares matrix = A' A
432 transpose_dot_matrix(A, A_square, m, n);
433 }
434
435 #ifdef _OPENMP
436 #pragma omp section
437 #endif
438 {
439 // Prepare the y square vector = A' y
440 transpose_dot_vector(A, y, y_square, m, n);
441 }
442 }
443
444
445 // Solve A' A x = A' y for x
446 if(solve_hermitian(A_square, y_square, n, checks))
447 {
448 err = 1;
449 goto error;
450 }
451 dt_simd_memcpy(y_square, y, n);
452
453error:
454 dt_free_align(y_square);
455 dt_free_align(A_square);
456
457 //clock_t end = clock();
458 //fprintf(stdout, "hermitian matrix solving took : %f s\n", ((float) (end - start)) / CLOCKS_PER_SEC);
459
460 return err;
461}
462
463#endif // DT_MATH_CHOLESKI_H
464
465// clang-format off
466// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
467// vim: shiftwidth=2 expandtab tabstop=2 cindent
468// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
469// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define m
Definition basecurve.c:283
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:376
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:355
static int choleski_decompose_safe(const float *const restrict A, float *const restrict L, size_t n)
Definition choleski.h:123
static int solve_hermitian(const float *const restrict A, float *const restrict y, const size_t n, const int checks)
Definition choleski.h:270
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:396
static int choleski_decompose_fast(const float *const restrict A, float *const restrict L, size_t n)
Definition choleski.h:97
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:241
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:221
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:171
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:191
static const float x
#define A(y, x)
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:72
@ DT_DEBUG_ALWAYS
Definition logging.h:49
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#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 macros.h:96
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static float * dt_alloc_align_float(size_t pixels)
Allocate pixels floats, cacheline-aligned and marked as such.
Definition mem_alloc.h:235
#define dt_omp_in_parallel()
Definition openmp.h:92