QGIS API Documentation 4.3.0-Master (d3b565c628d)
Loading...
Searching...
No Matches
qgsmatrixsolver.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmatrixsolver.cpp
3 ----------------------
4 begin : August 2026
5 copyright : (C) 2026 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16
17#include "qgsconfig.h"
18#include "qgsmatrixsolver.h"
19
20#include <mutex>
21
22#include "qgsexception.h"
23
24#include <QString>
25
26using namespace Qt::StringLiterals;
27
28#ifdef HAVE_GSL
29#define GSL_RANGE_CHECK_OFF
30#define HAVE_INLINE 1
31
32#include <gsl/gsl_matrix.h>
33#include <gsl/gsl_vector.h>
34#include <gsl/gsl_linalg.h>
35#include <gsl/gsl_errno.h>
36#endif
37
39{
41#ifdef HAVE_GSL
42 gsl_matrix *maxMatrix = nullptr;
43 gsl_vector *maxVectorB = nullptr;
44 gsl_vector *maxVectorX = nullptr;
45
46 // cached permutations for all possible sizes up to maxSize to avoid allocations
47 std::vector<gsl_permutation *> permutations;
48
49 gsl_matrix *matrixA_Copy = nullptr;
50 gsl_matrix *matrixV = nullptr;
51 gsl_vector *vectorS = nullptr;
52 gsl_vector *workSvd = nullptr;
53#endif
54};
55
57{
58#ifdef HAVE_GSL
59 return true;
60#else
61 return false;
62#endif
63}
64
66 : mData( std::make_unique<GslData>() )
67{
68 if ( maximumDimension <= 0 )
69 {
70 return;
71 }
72
73 mData->maximumDimension = maximumDimension;
74#ifdef HAVE_GSL
75 // disable GSL aborts for singular matrices -- we handle this via return code instead
76 static std::once_flag sGslErrorHandlerFlag;
77 std::call_once( sGslErrorHandlerFlag, []() { gsl_set_error_handler_off(); } );
78
79 mData->maxMatrix = gsl_matrix_calloc( maximumDimension, maximumDimension );
80 mData->maxVectorB = gsl_vector_calloc( maximumDimension );
81 mData->maxVectorX = gsl_vector_alloc( maximumDimension );
82
83 mData->matrixA_Copy = gsl_matrix_calloc( maximumDimension, maximumDimension );
84 mData->matrixV = gsl_matrix_calloc( maximumDimension, maximumDimension );
85 mData->vectorS = gsl_vector_calloc( maximumDimension );
86 mData->workSvd = gsl_vector_alloc( maximumDimension );
87
88 // Preallocate permutation arrays for all possible dynamic sizes 1 to maxSize
89 mData->permutations.resize( maximumDimension + 1, nullptr );
90 for ( int i = 1; i <= maximumDimension; ++i )
91 {
92 mData->permutations[i] = gsl_permutation_alloc( i );
93 }
94#endif
95}
96
98{
99#ifdef HAVE_GSL
100 if ( mData->maxMatrix )
101 {
102 gsl_matrix_free( mData->maxMatrix );
103 }
104 if ( mData->maxVectorB )
105 {
106 gsl_vector_free( mData->maxVectorB );
107 }
108 if ( mData->maxVectorX )
109 {
110 gsl_vector_free( mData->maxVectorX );
111 }
112 if ( mData->matrixA_Copy )
113 {
114 gsl_matrix_free( mData->matrixA_Copy );
115 }
116 if ( mData->matrixV )
117 {
118 gsl_matrix_free( mData->matrixV );
119 }
120 if ( mData->vectorS )
121 {
122 gsl_vector_free( mData->vectorS );
123 }
124 if ( mData->workSvd )
125 {
126 gsl_vector_free( mData->workSvd );
127 }
128
129 for ( auto *p : mData->permutations )
130 {
131 if ( p )
132 {
133 gsl_permutation_free( p );
134 }
135 }
136#endif
137}
138
140{
141 return mData->maximumDimension;
142}
143
144void QgsMatrixSolver::setValue( int row, int column, double value )
145{
146#ifdef HAVE_GSL
147 gsl_matrix_set( mData->maxMatrix, row, column, value );
148#else
149 ( void ) row;
150 ( void ) column;
151 ( void ) value;
152 throw QgsNotSupportedException( u"QgsMatrixSolver requires a QGIS build with GSL support enabled"_s );
153#endif
154}
155
156void QgsMatrixSolver::setRightHandSide( int row, double value )
157{
158#ifdef HAVE_GSL
159 gsl_vector_set( mData->maxVectorB, row, value );
160#else
161 ( void ) row;
162 ( void ) value;
163 throw QgsNotSupportedException( u"QgsMatrixSolver requires a QGIS build with GSL support enabled"_s );
164#endif
165}
166
167bool QgsMatrixSolver::solve( int dimension, QVector<double> &result, Qgis::LinearMatrixMethod method )
168{
169 if ( dimension <= 0 )
170 {
171 throw QgsInvalidArgumentException( u"Invalid value for dimension, must be > 0"_s );
172 }
173 if ( dimension > mData->maximumDimension )
174 {
175 throw QgsInvalidArgumentException( u"Invalid value for dimension, must be < %1"_s.arg( mData->maximumDimension ) );
176 }
177 switch ( method )
178 {
180 return solveLu( dimension, result, false );
181
183 return solveSvd( dimension, result, false );
184
186 {
187 if ( solveLu( dimension, result, true ) )
188 {
189 return true;
190 }
191 return solveSvd( dimension, result, false );
192 }
193 }
194
195 return false;
196}
197
198bool QgsMatrixSolver::solveLu( int dimension, QVector<double> &result, bool retainOriginalMatrices )
199{
200#ifdef HAVE_GSL
201 // create views into the preallocated memory block
202 gsl_matrix_view A_view = gsl_matrix_submatrix( mData->maxMatrix, 0, 0, dimension, dimension );
203 gsl_matrix_view A_copy = gsl_matrix_submatrix( mData->matrixA_Copy, 0, 0, dimension, dimension );
204
205 gsl_vector_view B_view = gsl_vector_subvector( mData->maxVectorB, 0, dimension );
206 gsl_vector_view X_view = gsl_vector_subvector( mData->maxVectorX, 0, dimension );
207
208 gsl_matrix *A = &A_view.matrix;
209 if ( retainOriginalMatrices )
210 {
211 gsl_matrix_memcpy( &A_copy.matrix, &A_view.matrix );
212 A = &A_copy.matrix;
213 }
214
215 gsl_permutation *p = mData->permutations[dimension];
216 int signum = 0;
217 gsl_linalg_LU_decomp( A, p, &signum );
218 if ( gsl_linalg_LU_solve( A, p, &B_view.vector, &X_view.vector ) != 0 )
219 {
220 return false;
221 }
222
223 result.resize( dimension );
224 for ( int i = 0; i < dimension; ++i )
225 {
226 result[i] = gsl_vector_get( &X_view.vector, i );
227 }
228
229 return true;
230#else
231 ( void ) dimension;
232 ( void ) result;
233 throw QgsNotSupportedException( u"QgsMatrixSolver requires a QGIS build with GSL support enabled"_s );
234#endif
235}
236
237bool QgsMatrixSolver::solveSvd( int dimension, QVector<double> &result, bool retainOriginalMatrices )
238{
239#ifdef HAVE_GSL
240 gsl_matrix_view A_view = gsl_matrix_submatrix( mData->maxMatrix, 0, 0, dimension, dimension );
241 gsl_matrix_view A_copy = gsl_matrix_submatrix( mData->matrixA_Copy, 0, 0, dimension, dimension );
242 gsl_matrix_view V = gsl_matrix_submatrix( mData->matrixV, 0, 0, dimension, dimension );
243 gsl_vector_view S = gsl_vector_subvector( mData->vectorS, 0, dimension );
244 gsl_vector_view b = gsl_vector_subvector( mData->maxVectorB, 0, dimension );
245 gsl_vector_view x = gsl_vector_subvector( mData->maxVectorX, 0, dimension );
246 gsl_vector_view work = gsl_vector_subvector( mData->workSvd, 0, dimension );
247
248 gsl_matrix *A = &A_view.matrix;
249 if ( retainOriginalMatrices )
250 {
251 gsl_matrix_memcpy( &A_copy.matrix, &A_view.matrix );
252 A = &A_copy.matrix;
253 }
254
255 // Compute SVD: A_copy = U * S * V^T
256 if ( gsl_linalg_SV_decomp( A, &V.matrix, &S.vector, &work.vector ) != 0 )
257 {
258 return false;
259 }
260
261 // Solve SVD: U * S * V^T * x = b with singular value truncation
262 // gsl_linalg_SV_solve automatically uses pseudo-inverse for singular values < tol
263 if ( gsl_linalg_SV_solve( A, &V.matrix, &S.vector, &b.vector, &x.vector ) != 0 )
264 {
265 return false;
266 }
267
268 result.resize( dimension );
269 for ( int i = 0; i < dimension; ++i )
270 {
271 result[i] = gsl_vector_get( &x.vector, i );
272 }
273
274 return true;
275#else
276 ( void ) dimension;
277 ( void ) result;
278 throw QgsNotSupportedException( u"QgsMatrixSolver requires a QGIS build with GSL support enabled"_s );
279#endif
280}
LinearMatrixMethod
Mathematical methods to use for solving linear matrix equations.
Definition qgis.h:7095
@ Lu
Fast lower-upper (LU) decomposition (fails on singular/collinear matrices).
Definition qgis.h:7096
@ LuWithSvdFallback
Try LU first; fallback to SVD on singularity.
Definition qgis.h:7098
@ Svd
Singular Value Decomposition (handles collinearity and rank deficiency).
Definition qgis.h:7097
Custom exception class when argument are invalid.
void setRightHandSide(int row, double value)
Sets a value in the preallocated right-hand-side vector b.
QgsMatrixSolver(int maximumDimension)
Constructor for QgsMatrixSolver, pre-allocated to solve matrices with the specified maximumDimension.
bool solve(int dimension, QVector< double > &result, Qgis::LinearMatrixMethod method=Qgis::LinearMatrixMethod::Lu)
Solves the system Ax = b for a specific active dimension.
void setValue(int row, int column, double value)
Sets the value for row, column in the preallocated matrix A.
int maximumDimension() const
Returns the maximum dimension supported by this solver.
static bool isAvailable()
Returns true if the matrix solver functionality is available on the current system.
Custom exception class which is raised when an operation is not supported.