DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
math.h
1// Copyright (C) 2021 Igor Baratta
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 "types.h"
10#include <algorithm>
11#include <array>
12#include <basix/mdspan.hpp>
13#include <cassert>
14#include <cmath>
15#include <concepts>
16#include <format>
17#include <stdexcept>
18#include <string>
19
20namespace dolfinx::math
21{
22
27template <typename U, typename V>
28 requires scalar<typename U::value_type>
29 && std::same_as<typename U::value_type, typename V::value_type>
30constexpr std::array<typename U::value_type, 3> cross(const U& u, const V& v)
31{
32 assert(u.size() == 3);
33 assert(v.size() == 3);
34 return {u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2],
35 u[0] * v[1] - u[1] * v[0]};
36}
37
40// TODO: mark constexpr with C++23 (std::fma becomes constexpr).
41template <std::floating_point T>
42T difference_of_products(T a, T b, T c, T d) noexcept
43{
44 T w = b * c;
45 T err = std::fma(-b, c, w);
46 T diff = std::fma(a, d, -w);
47 return diff + err;
48}
49
56// TODO: mark constexpr with C++23 (relies on std::fma, constexpr from C++23).
57template <std::floating_point T>
58auto det(const T* A, std::array<std::size_t, 2> shape)
59{
60 assert(shape[0] == shape[1]);
61
62 switch (shape[0])
63 {
64 case 1:
65 return *A;
66 case 2:
67 /* A(0, 0), A(0, 1), A(1, 0), A(1, 1) */
68 return difference_of_products(A[0], A[1], A[2], A[3]);
69 case 3:
70 {
71 // Leibniz formula combined with Kahan’s method for accurate
72 // computation of 3 x 3 determinants
73 T w0 = difference_of_products(A[3 + 1], A[3 + 2], A[3 * 2 + 1],
74 A[2 * 3 + 2]);
75 T w1 = difference_of_products(A[3], A[3 + 2], A[3 * 2], A[3 * 2 + 2]);
76 T w2 = difference_of_products(A[3], A[3 + 1], A[3 * 2], A[3 * 2 + 1]);
77 T w3 = difference_of_products(A[0], A[1], w1, w0);
78 T w4 = std::fma(A[2], w2, w3);
79 return w4;
80 }
81 default:
82 throw std::runtime_error(
83 std::format("math::det is not implemented for {}x{} matrices.",
84 shape[0], shape[1]));
85 }
86}
87
92// TODO: mark constexpr with C++23 (relies on std::fma, constexpr from C++23).
93template <typename Matrix>
94 requires std::floating_point<typename Matrix::value_type>
95auto det(Matrix A)
96{
97 static_assert(Matrix::rank() == 2, "Must be rank 2");
98 assert(A.extent(0) == A.extent(1));
99
100 using value_type = typename Matrix::value_type;
101 const int nrows = A.extent(0);
102 switch (nrows)
103 {
104 case 1:
105 return A(0, 0);
106 case 2:
107 return difference_of_products(A(0, 0), A(0, 1), A(1, 0), A(1, 1));
108 case 3:
109 {
110 // Leibniz formula combined with Kahan’s method for accurate
111 // computation of 3 x 3 determinants
112 value_type w0 = difference_of_products(A(1, 1), A(1, 2), A(2, 1), A(2, 2));
113 value_type w1 = difference_of_products(A(1, 0), A(1, 2), A(2, 0), A(2, 2));
114 value_type w2 = difference_of_products(A(1, 0), A(1, 1), A(2, 0), A(2, 1));
115 value_type w3 = difference_of_products(A(0, 0), A(0, 1), w1, w0);
116 value_type w4 = std::fma(A(0, 2), w2, w3);
117 return w4;
118 }
119 default:
120 throw std::runtime_error(
121 std::format("math::det is not implemented for {}x{} matrices.",
122 A.extent(0), A.extent(1)));
123 }
124}
125
132// TODO: mark constexpr with C++23 (relies on std::fma, constexpr from C++23).
133template <typename U, typename V>
134 requires std::floating_point<typename U::value_type>
135void inv(U A, V B)
136{
137 static_assert(U::rank() == 2, "Must be rank 2");
138 static_assert(V::rank() == 2, "Must be rank 2");
139
140 using value_type = typename U::value_type;
141 const std::size_t nrows = A.extent(0);
142 switch (nrows)
143 {
144 case 1:
145 B(0, 0) = value_type{1} / A(0, 0);
146 break;
147 case 2:
148 {
149 value_type idet = 1. / det(A);
150 B(0, 0) = idet * A(1, 1);
151 B(0, 1) = -idet * A(0, 1);
152 B(1, 0) = -idet * A(1, 0);
153 B(1, 1) = idet * A(0, 0);
154 break;
155 }
156 case 3:
157 {
158 value_type w0 = difference_of_products(A(1, 1), A(1, 2), A(2, 1), A(2, 2));
159 value_type w1 = difference_of_products(A(1, 0), A(1, 2), A(2, 0), A(2, 2));
160 value_type w2 = difference_of_products(A(1, 0), A(1, 1), A(2, 0), A(2, 1));
161 value_type w3 = difference_of_products(A(0, 0), A(0, 1), w1, w0);
162 value_type det = std::fma(A(0, 2), w2, w3);
163 assert(det != 0.);
164 value_type idet = 1 / det;
165
166 B(0, 0) = w0 * idet;
167 B(1, 0) = -w1 * idet;
168 B(2, 0) = w2 * idet;
169 B(0, 1) = difference_of_products(A(0, 2), A(0, 1), A(2, 2), A(2, 1)) * idet;
170 B(0, 2) = difference_of_products(A(0, 1), A(0, 2), A(1, 1), A(1, 2)) * idet;
171 B(1, 1) = difference_of_products(A(0, 0), A(0, 2), A(2, 0), A(2, 2)) * idet;
172 B(1, 2) = difference_of_products(A(1, 0), A(0, 0), A(1, 2), A(0, 2)) * idet;
173 B(2, 1) = difference_of_products(A(2, 0), A(0, 0), A(2, 1), A(0, 1)) * idet;
174 B(2, 2) = difference_of_products(A(0, 0), A(1, 0), A(0, 1), A(1, 1)) * idet;
175 break;
176 }
177 default:
178 throw std::runtime_error(
179 std::format("math::inv is not implemented for {}x{} matrices.",
180 A.extent(0), A.extent(1)));
181 }
182}
183
190template <typename U, typename V, typename P>
191 requires scalar<typename U::value_type> && scalar<typename V::value_type>
192 && scalar<typename P::value_type>
193constexpr void dot(U A, V B, P C, bool transpose = false)
194{
195 static_assert(U::rank() == 2, "Must be rank 2");
196 static_assert(V::rank() == 2, "Must be rank 2");
197 static_assert(P::rank() == 2, "Must be rank 2");
198
199 if (transpose)
200 {
201 assert(A.extent(0) == B.extent(1));
202 for (std::size_t i = 0; i < A.extent(1); i++)
203 for (std::size_t j = 0; j < B.extent(0); j++)
204 for (std::size_t k = 0; k < A.extent(0); k++)
205 C(i, j) += A(k, i) * B(j, k);
206 }
207 else
208 {
209 assert(A.extent(1) == B.extent(0));
210 for (std::size_t i = 0; i < A.extent(0); i++)
211 for (std::size_t j = 0; j < B.extent(1); j++)
212 for (std::size_t k = 0; k < A.extent(1); k++)
213 C(i, j) += A(i, k) * B(k, j);
214 }
215}
216
223// TODO: mark constexpr with C++23 (relies on std::fma via inv, constexpr from
224// C++23).
225template <typename U, typename V>
226 requires std::floating_point<typename U::value_type>
227void pinv(U A, V P)
228{
229 static_assert(U::rank() == 2, "Must be rank 2");
230 static_assert(V::rank() == 2, "Must be rank 2");
231
232 assert(A.extent(0) > A.extent(1));
233 assert(P.extent(1) == A.extent(0));
234 assert(P.extent(0) == A.extent(1));
235 using T = typename U::value_type;
236 if (A.extent(1) == 2)
237 {
238 // Fixed-size buffers below assume A is 3x2
239 assert(A.extent(0) == 3);
240 std::array<T, 6> ATb;
241 std::array<T, 4> ATAb, Invb;
242 md::mdspan<T, md::extents<std::size_t, 2, 3>> AT(ATb.data(), 2, 3);
243 md::mdspan<T, md::extents<std::size_t, 2, 2>> ATA(ATAb.data(), 2, 2);
244 md::mdspan<T, md::extents<std::size_t, 2, 2>> Inv(Invb.data(), 2, 2);
245
246 for (std::size_t i = 0; i < AT.extent(0); ++i)
247 for (std::size_t j = 0; j < AT.extent(1); ++j)
248 AT(i, j) = A(j, i);
249
250 std::ranges::fill(ATAb, 0.0);
251 for (std::size_t i = 0; i < P.extent(0); ++i)
252 for (std::size_t j = 0; j < P.extent(1); ++j)
253 P(i, j) = 0;
254
255 // pinv(A) = (A^T * A)^-1 * A^T
256 dot(AT, A, ATA);
257 inv(ATA, Inv);
258 dot(Inv, AT, P);
259 }
260 else if (A.extent(1) == 1)
261 {
262 T res = 0;
263 for (std::size_t i = 0; i < A.extent(0); ++i)
264 for (std::size_t j = 0; j < A.extent(1); ++j)
265 res += A(i, j) * A(i, j);
266
267 for (std::size_t i = 0; i < A.extent(0); ++i)
268 for (std::size_t j = 0; j < A.extent(1); ++j)
269 P(j, i) = (1 / res) * A(i, j);
270 }
271 else
272 {
273 throw std::runtime_error(
274 std::format("math::pinv is not implemented for {}x{} matrices.",
275 A.extent(0), A.extent(1)));
276 }
277}
278
279} // namespace dolfinx::math