Basix 0.12.0.dev0
Loading...
Searching...
No Matches
math.h
1// Copyright (C) 2021-2024 Igor Baratta and Garth N. Wells
2//
3// This file is part of DOLFINx (https://www.fenicsproject.org)
4//
5// SPDX-License-Identifier: LGPL-3.0-or-later
6
7#pragma once
8
9#include "mdspan.hpp"
10#include "types.h"
11#include <algorithm>
12#include <array>
13#include <cmath>
14#include <concepts>
15#include <span>
16#include <stdexcept>
17#include <string>
18#include <utility>
19#include <vector>
20
21extern "C"
22{
23 void ssyevd_(char* jobz, char* uplo, int* n, float* a, int* lda, float* w,
24 float* work, int* lwork, int* iwork, int* liwork, int* info);
25 void dsyevd_(char* jobz, char* uplo, int* n, double* a, int* lda, double* w,
26 double* work, int* lwork, int* iwork, int* liwork, int* info);
27
28 void sgesv_(int* N, int* NRHS, float* A, int* LDA, int* IPIV, float* B,
29 int* LDB, int* INFO);
30 void dgesv_(int* N, int* NRHS, double* A, int* LDA, int* IPIV, double* B,
31 int* LDB, int* INFO);
32
33 void sgemm_(char* transa, char* transb, int* m, int* n, int* k, float* alpha,
34 float* a, int* lda, float* b, int* ldb, float* beta, float* c,
35 int* ldc);
36 void dgemm_(char* transa, char* transb, int* m, int* n, int* k, double* alpha,
37 double* a, int* lda, double* b, int* ldb, double* beta, double* c,
38 int* ldc);
39
40 int sgetrf_(const int* m, const int* n, float* a, const int* lda, int* lpiv,
41 int* info);
42 int dgetrf_(const int* m, const int* n, double* a, const int* lda, int* lpiv,
43 int* info);
44}
45
50namespace basix::math
51{
52namespace impl
53{
63template <std::floating_point T>
64void dot_blas(std::span<const T> A, std::array<std::size_t, 2> Ashape,
65 std::span<const T> B, std::array<std::size_t, 2> Bshape,
66 std::span<T> C, T alpha = 1, T beta = 0)
67{
68 static_assert(std::is_same_v<T, float> or std::is_same_v<T, double>);
69
70 assert(Ashape[1] == Bshape[0]);
71 assert(C.size() == Ashape[0] * Bshape[1]);
72
73 int M = Ashape[0];
74 int N = Bshape[1];
75 int K = Ashape[1];
76
77 int lda = K;
78 int ldb = N;
79 int ldc = N;
80 char trans = 'N';
81 if constexpr (std::is_same_v<T, float>)
82 {
83 sgemm_(&trans, &trans, &N, &M, &K, &alpha, const_cast<T*>(B.data()), &ldb,
84 const_cast<T*>(A.data()), &lda, &beta, C.data(), &ldc);
85 }
86 else if constexpr (std::is_same_v<T, double>)
87 {
88 dgemm_(&trans, &trans, &N, &M, &K, &alpha, const_cast<T*>(B.data()), &ldb,
89 const_cast<T*>(A.data()), &lda, &beta, C.data(), &ldc);
90 }
91}
92
93} // namespace impl
94
99template <typename U, typename V>
100std::pair<std::vector<typename U::value_type>, std::array<std::size_t, 2>>
101outer(const U& u, const V& v)
102{
103 std::vector<typename U::value_type> result(u.size() * v.size());
104 for (std::size_t i = 0; i < u.size(); ++i)
105 for (std::size_t j = 0; j < v.size(); ++j)
106 result[i * v.size() + j] = u[i] * v[j];
107 return {std::move(result), {u.size(), v.size()}};
108}
109
114template <typename U, typename V>
115std::array<typename U::value_type, 3> cross(const U& u, const V& v)
116{
117 assert(u.size() == 3);
118 assert(v.size() == 3);
119 return {u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2],
120 u[0] * v[1] - u[1] * v[0]};
121}
122
130template <std::floating_point T>
131std::pair<std::vector<T>, std::vector<T>> eigh(std::span<const T> A,
132 std::size_t n)
133{
134 // Copy A
135 std::vector<T> M(A.begin(), A.end());
136
137 // Allocate storage for eigenvalues
138 std::vector<T> w(n, 0);
139
140 int N = n;
141 char jobz = 'V'; // Compute eigenvalues and eigenvectors
142 char uplo = 'L'; // Lower
143 int ldA = n;
144 int lwork = -1;
145 int liwork = -1;
146 int info;
147 std::vector<T> work(1);
148 std::vector<int> iwork(1);
149
150 // Query optimal workspace size
151 if constexpr (std::is_same_v<T, float>)
152 {
153 ssyevd_(&jobz, &uplo, &N, M.data(), &ldA, w.data(), work.data(), &lwork,
154 iwork.data(), &liwork, &info);
155 }
156 else if constexpr (std::is_same_v<T, double>)
157 {
158 dsyevd_(&jobz, &uplo, &N, M.data(), &ldA, w.data(), work.data(), &lwork,
159 iwork.data(), &liwork, &info);
160 }
161
162 if (info != 0)
163 throw std::runtime_error("Could not find workspace size for syevd.");
164
165 // Solve eigen problem
166 work.resize(work[0]);
167 iwork.resize(iwork[0]);
168 lwork = work.size();
169 liwork = iwork.size();
170 if constexpr (std::is_same_v<T, float>)
171 {
172 ssyevd_(&jobz, &uplo, &N, M.data(), &ldA, w.data(), work.data(), &lwork,
173 iwork.data(), &liwork, &info);
174 }
175 else if constexpr (std::is_same_v<T, double>)
176 {
177 dsyevd_(&jobz, &uplo, &N, M.data(), &ldA, w.data(), work.data(), &lwork,
178 iwork.data(), &liwork, &info);
179 }
180 if (info != 0)
181 throw std::runtime_error("Eigenvalue computation did not converge.");
182
183 return {std::move(w), std::move(M)};
184}
185
190template <std::floating_point T>
191std::vector<T> solve(md::mdspan<const T, md::dextents<std::size_t, 2>> A,
192 md::mdspan<const T, md::dextents<std::size_t, 2>> B)
193{
194 // Copy A and B to column-major storage
195 mdex::mdarray<T, md::dextents<std::size_t, 2>, md::layout_left> _A(
196 A.extents()),
197 _B(B.extents());
198 for (std::size_t i = 0; i < A.extent(0); ++i)
199 for (std::size_t j = 0; j < A.extent(1); ++j)
200 _A(i, j) = A(i, j);
201 for (std::size_t i = 0; i < B.extent(0); ++i)
202 for (std::size_t j = 0; j < B.extent(1); ++j)
203 _B(i, j) = B(i, j);
204
205 int N = _A.extent(0);
206 int nrhs = _B.extent(1);
207 int lda = _A.extent(0);
208 int ldb = _B.extent(0);
209
210 // Pivot indices that define the permutation matrix for the LU solver
211 std::vector<int> piv(N);
212 int info;
213 if constexpr (std::is_same_v<T, float>)
214 sgesv_(&N, &nrhs, _A.data(), &lda, piv.data(), _B.data(), &ldb, &info);
215 else if constexpr (std::is_same_v<T, double>)
216 dgesv_(&N, &nrhs, _A.data(), &lda, piv.data(), _B.data(), &ldb, &info);
217 if (info != 0)
218 throw std::runtime_error("Call to dgesv failed: " + std::to_string(info));
219
220 // Copy result to row-major storage
221 std::vector<T> rb(_B.extent(0) * _B.extent(1));
222 md::mdspan<T, md::dextents<std::size_t, 2>> r(rb.data(), _B.extents());
223 for (std::size_t i = 0; i < _B.extent(0); ++i)
224 for (std::size_t j = 0; j < _B.extent(1); ++j)
225 r(i, j) = _B(i, j);
226
227 return rb;
228}
229
233template <std::floating_point T>
234bool is_singular(md::mdspan<const T, md::dextents<std::size_t, 2>> A)
235{
236 // Copy to column major matrix
237 mdex::mdarray<T, md::dextents<std::size_t, 2>, md::layout_left> _A(
238 A.extents());
239 for (std::size_t i = 0; i < A.extent(0); ++i)
240 for (std::size_t j = 0; j < A.extent(1); ++j)
241 _A(i, j) = A(i, j);
242
243 std::vector<T> B(A.extent(1), 1);
244 int N = _A.extent(0);
245 int nrhs = 1;
246 int lda = _A.extent(0);
247 int ldb = B.size();
248
249 // Pivot indices that define the permutation matrix for the LU solver
250 std::vector<int> piv(N);
251 int info;
252 if constexpr (std::is_same_v<T, float>)
253 sgesv_(&N, &nrhs, _A.data(), &lda, piv.data(), B.data(), &ldb, &info);
254 else if constexpr (std::is_same_v<T, double>)
255 dgesv_(&N, &nrhs, _A.data(), &lda, piv.data(), B.data(), &ldb, &info);
256
257 if (info < 0)
258 {
259 throw std::runtime_error("dgesv failed due to invalid value: "
260 + std::to_string(info));
261 }
262 else if (info > 0)
263 return true;
264 else
265 return false;
266}
267
273template <std::floating_point T>
274std::vector<std::size_t>
275transpose_lu(std::pair<std::vector<T>, std::array<std::size_t, 2>>& A)
276{
277 std::size_t dim = A.second[0];
278 assert(dim == A.second[1]);
279 int N = dim;
280 int info;
281 std::vector<int> lu_perm(dim);
282
283 // Comput LU decomposition of M
284 if constexpr (std::is_same_v<T, float>)
285 sgetrf_(&N, &N, A.first.data(), &N, lu_perm.data(), &info);
286 else if constexpr (std::is_same_v<T, double>)
287 dgetrf_(&N, &N, A.first.data(), &N, lu_perm.data(), &info);
288
289 if (info != 0)
290 {
291 throw std::runtime_error("LU decomposition failed: "
292 + std::to_string(info));
293 }
294
295 std::vector<std::size_t> perm(dim);
296 for (std::size_t i = 0; i < dim; ++i)
297 perm[i] = static_cast<std::size_t>(lu_perm[i] - 1);
298
299 return perm;
300}
301
309template <typename U, typename V, typename W>
310void dot(const U& A, const V& B, W&& C,
311 typename std::decay_t<U>::value_type alpha = 1,
312 typename std::decay_t<U>::value_type beta = 0)
313{
314 using T = typename std::decay_t<U>::value_type;
315
316 assert(A.extent(1) == B.extent(0));
317 assert(C.extent(0) == A.extent(0));
318 assert(C.extent(1) == B.extent(1));
319 if (A.extent(0) * B.extent(1) * A.extent(1) < 256)
320 {
321 for (std::size_t i = 0; i < A.extent(0); ++i)
322 {
323 for (std::size_t j = 0; j < B.extent(1); ++j)
324 {
325 T C0 = C(i, j);
326 C(i, j) = 0;
327 T& _C = C(i, j);
328 for (std::size_t k = 0; k < A.extent(1); ++k)
329 _C += A(i, k) * B(k, j);
330 _C = alpha * _C + beta * C0;
331 }
332 }
333 }
334 else
335 {
336 static_assert(std::is_same_v<typename std::decay_t<U>::layout_type,
337 md::layout_right>);
338 static_assert(std::is_same_v<typename std::decay_t<V>::layout_type,
339 md::layout_right>);
340 static_assert(std::is_same_v<typename std::decay_t<W>::layout_type,
341 md::layout_right>);
342 static_assert(std::is_same_v<typename std::decay_t<V>::value_type, T>);
343 static_assert(std::is_same_v<typename std::decay_t<W>::value_type, T>);
344 impl::dot_blas<T>(
345 std::span(A.data_handle(), A.size()), {A.extent(0), A.extent(1)},
346 std::span(B.data_handle(), B.size()), {B.extent(0), B.extent(1)},
347 std::span(C.data_handle(), C.size()), alpha, beta);
348 }
349}
350
354template <std::floating_point T>
355std::vector<T> eye(std::size_t n)
356{
357 std::vector<T> I(n * n, 0);
358 md::mdspan<T, md::dextents<std::size_t, 2>> Iview(I.data(), n, n);
359 for (std::size_t i = 0; i < n; ++i)
360 Iview(i, i) = 1;
361 return I;
362}
363
368template <std::floating_point T>
369void orthogonalise(md::mdspan<T, md::dextents<std::size_t, 2>> wcoeffs,
370 std::size_t start = 0)
371{
372 using mdspan2_t = md::mdspan<T, md::dextents<std::size_t, 2>>;
373 using cmdspan2_t = md::mdspan<const T, md::dextents<std::size_t, 2>>;
374
375 const std::size_t ndofs = wcoeffs.extent(0);
376 const std::size_t psize = wcoeffs.extent(1);
377
378 // Classical Gram-Schmidt: after normalising row i, its projection onto
379 // every later row is subtracted. Rather than looping over each row j,
380 // the projection coefficients for all remaining rows are computed as a
381 // single matrix-vector product, and the subtraction as a single rank-1
382 // update, both via BLAS (math::dot).
383 std::vector<T> a_vec;
384 std::vector<T> update;
385 for (std::size_t i = start; i < ndofs; ++i)
386 {
387 T norm = 0;
388 for (std::size_t k = 0; k < psize; ++k)
389 norm += wcoeffs(i, k) * wcoeffs(i, k);
390
391 norm = std::sqrt(norm);
392 if (norm < 2 * std::numeric_limits<T>::epsilon())
393 {
394 throw std::runtime_error("Cannot orthogonalise the rows of a matrix "
395 "with incomplete row rank");
396 }
397
398 for (std::size_t k = 0; k < psize; ++k)
399 wcoeffs(i, k) /= norm;
400
401 const std::size_t nrem = ndofs - i - 1;
402 if (nrem == 0)
403 continue;
404
405 // Row i viewed as a (psize, 1) column and a (1, psize) row (both are
406 // just the same contiguous length-psize data), and the remaining
407 // rows viewed as an (nrem, psize) block.
408 cmdspan2_t row_col(&wcoeffs(i, 0), psize, 1);
409 cmdspan2_t row_row(&wcoeffs(i, 0), 1, psize);
410 cmdspan2_t tail(&wcoeffs(i + 1, 0), nrem, psize);
411
412 // a = tail * row_i
413 a_vec.resize(nrem);
414 mdspan2_t a_view(a_vec.data(), nrem, 1);
416
417 // update = a * row_i^T
418 update.resize(nrem * psize);
421
422 mdspan2_t tail_mut(&wcoeffs(i + 1, 0), nrem, psize);
423 for (std::size_t j = 0; j < nrem; ++j)
424 for (std::size_t k = 0; k < psize; ++k)
425 tail_mut(j, k) -= update_view(j, k);
426 }
427}
428} // namespace basix::math
A finite element.
Definition finite-element.h:138
Mathematical functions.
Definition math.h:51
bool is_singular(md::mdspan< const T, md::dextents< std::size_t, 2 > > A)
Check if A is a singular matrix.
Definition math.h:234
std::array< typename U::value_type, 3 > cross(const U &u, const V &v)
Compute the cross product u x v.
Definition math.h:115
std::vector< std::size_t > transpose_lu(std::pair< std::vector< T >, std::array< std::size_t, 2 > > &A)
Compute the LU decomposition of the transpose of a square matrix A.
Definition math.h:275
void orthogonalise(md::mdspan< T, md::dextents< std::size_t, 2 > > wcoeffs, std::size_t start=0)
Orthonormalise the rows of a matrix (in place).
Definition math.h:369
std::vector< T > solve(md::mdspan< const T, md::dextents< std::size_t, 2 > > A, md::mdspan< const T, md::dextents< std::size_t, 2 > > B)
Solve A X = B.
Definition math.h:191
void dot(const U &A, const V &B, W &&C, typename std::decay_t< U >::value_type alpha=1, typename std::decay_t< U >::value_type beta=0)
Compute C = alpha A * B + beta C.
Definition math.h:310
std::pair< std::vector< T >, std::vector< T > > eigh(std::span< const T > A, std::size_t n)
Compute the eigenvalues and eigenvectors of a square symmetric matrix A.
Definition math.h:131
std::vector< T > eye(std::size_t n)
Build an identity matrix.
Definition math.h:355
std::pair< std::vector< typename U::value_type >, std::array< std::size_t, 2 > > outer(const U &u, const V &v)
Compute the outer product of vectors u and v.
Definition math.h:101