TRIQS/nda 1.3.0
Multi-dimensional array library for C++
Loading...
Searching...
No Matches
det_and_inverse.hpp
Go to the documentation of this file.
1// Copyright (c) 2019--present, The Simons Foundation
2// This file is part of TRIQS/nda and is licensed under the Apache License, Version 2.0.
3// SPDX-License-Identifier: Apache-2.0
4// See LICENSE in the root of this distribution for details.
5
10
11#pragma once
12
13#include "../basic_array.hpp"
15#include "../clef/make_lazy.hpp"
16#include "../concepts.hpp"
17#include "../exceptions.hpp"
18#include "../lapack/getrf.hpp"
19#include "../lapack/getri.hpp"
23#include "../mem/policies.hpp"
24#include "../print.hpp"
25#include "../traits.hpp"
26
27#include <iostream>
28#include <type_traits>
29#include <utility>
30
31namespace nda {
32
37
49 template <typename A>
50 bool is_matrix_square(A const &a, bool print_error = false) {
51 bool r = (a.shape()[0] == a.shape()[1]);
52 if (not r and print_error)
53 std::cerr << "Error in nda::detail::is_matrix_square: Dimensions are: (" << a.shape()[0] << "," << a.shape()[1] << ")\n" << std::endl;
54 return r;
55 }
56
68 template <typename A>
69 bool is_matrix_diagonal(A const &a, bool print_error = false) {
70 bool r = is_matrix_square(a) and a == diag(diagonal(a));
71 if (not r and print_error) std::cerr << "Error in nda::detail::is_matrix_diagonal: Non-diagonal matrix: " << a << std::endl;
72 return r;
73 }
74
88 template <typename M>
91 {
92 using value_t = get_value_t<M>;
93 static_assert(std::is_convertible_v<value_t, double> or std::is_convertible_v<value_t, std::complex<double>>,
94 "Error in nda::determinant_in_place: Value type needs to be convertible to double or std::complex<double>");
95 static_assert(not std::is_const_v<M>, "Error in nda::determinant_in_place: Value type cannot be const");
96
97 // special case for an empty matrix
98 if (m.empty()) return value_t{1};
99
100 // check if the matrix is square
101 if (m.extent(0) != m.extent(1)) NDA_RUNTIME_ERROR << "Error in nda::determinant_in_place: Matrix is not square: " << m.shape();
102
103 // calculate the LU decomposition using lapack getrf
104 const int dim = m.extent(0);
105 basic_array<int, 1, C_layout, 'A', sso<100>> ipiv(dim);
106 int info = lapack::getrf(m, ipiv); // it is ok to be in C order
107 if (info < 0) NDA_RUNTIME_ERROR << "Error in nda::determinant_in_place: info = " << info;
108
109 // calculate the determinant from the LU decomposition
110 auto det = value_t{1};
111 int n_flips = 0;
112 for (int i = 0; i < dim; i++) {
113 det *= m(i, i);
114 // count the number of column interchanges performed by getrf
115 if (ipiv(i) != i + 1) ++n_flips;
116 }
117
118 return ((n_flips % 2 == 1) ? -det : det);
119 }
120
131 template <typename M>
132 auto determinant(M const &m) {
133 auto m_copy = make_regular(m);
134 return determinant_in_place(m_copy);
135 }
136
137 // For small matrices (2x2 and 3x3), we directly
138 // compute the matrix inversion rather than calling the
139 // LaPack routine
140 // ---------- Small Inverse Benchmarks ---------
141 // Run on (16 X 2400 MHz CPUs) (see benchmarks/small_inv.cpp)
142 // ---------------------------------------------
143 // Matrix Size Time (old) Time (new)
144 // 1 502 ns 59.0 ns
145 // 2 595 ns 61.7 ns
146 // 3 701 ns 67.5 ns
147
156 template <MemoryMatrix M>
157 requires(get_algebra<M> == 'M' and mem::on_host<M>)
158 void inverse1_in_place(M &&m) { // NOLINT (temporary views are allowed here)
159 if (m(0, 0) == 0.0) NDA_RUNTIME_ERROR << "Error in nda::inverse1_in_place: Matrix is not invertible";
160 m(0, 0) = 1.0 / m(0, 0);
161 }
162
171 template <MemoryMatrix M>
172 requires(get_algebra<M> == 'M' and mem::on_host<M>)
173 void inverse2_in_place(M &&m) { // NOLINT (temporary views are allowed here)
174 // calculate the adjoint of the matrix
175 std::swap(m(0, 0), m(1, 1));
176
177 // calculate the inverse determinant of the matrix
178 auto det = (m(0, 0) * m(1, 1) - m(0, 1) * m(1, 0));
179 if (det == 0.0) NDA_RUNTIME_ERROR << "Error in nda::inverse2_in_place: Matrix is not invertible";
180 auto detinv = 1.0 / det;
181
182 // multiply the adjoint by the inverse determinant
183 m(0, 0) *= +detinv;
184 m(1, 1) *= +detinv;
185 m(1, 0) *= -detinv;
186 m(0, 1) *= -detinv;
187 }
188
197 template <MemoryMatrix M>
198 requires(get_algebra<M> == 'M' and mem::on_host<M>)
199 void inverse3_in_place(M &&m) { // NOLINT (temporary views are allowed here)
200 // calculate the cofactors of the matrix
201 auto b00 = +m(1, 1) * m(2, 2) - m(1, 2) * m(2, 1);
202 auto b10 = -m(1, 0) * m(2, 2) + m(1, 2) * m(2, 0);
203 auto b20 = +m(1, 0) * m(2, 1) - m(1, 1) * m(2, 0);
204 auto b01 = -m(0, 1) * m(2, 2) + m(0, 2) * m(2, 1);
205 auto b11 = +m(0, 0) * m(2, 2) - m(0, 2) * m(2, 0);
206 auto b21 = -m(0, 0) * m(2, 1) + m(0, 1) * m(2, 0);
207 auto b02 = +m(0, 1) * m(1, 2) - m(0, 2) * m(1, 1);
208 auto b12 = -m(0, 0) * m(1, 2) + m(0, 2) * m(1, 0);
209 auto b22 = +m(0, 0) * m(1, 1) - m(0, 1) * m(1, 0);
210
211 // calculate the inverse determinant of the matrix
212 auto det = m(0, 0) * b00 + m(0, 1) * b10 + m(0, 2) * b20;
213 if (det == 0.0) NDA_RUNTIME_ERROR << "Error in nda::inverse3_in_place: Matrix is not invertible";
214 auto detinv = 1.0 / det;
215
216 // fill the matrix by multiplying the cofactors by the inverse determinant
217 m(0, 0) = detinv * b00;
218 m(0, 1) = detinv * b01;
219 m(0, 2) = detinv * b02;
220 m(1, 0) = detinv * b10;
221 m(1, 1) = detinv * b11;
222 m(1, 2) = detinv * b12;
223 m(2, 0) = detinv * b20;
224 m(2, 1) = detinv * b21;
225 m(2, 2) = detinv * b22;
226 }
227
241 template <MemoryMatrix M>
242 requires(get_algebra<M> == 'M')
243 void inverse_in_place(M &&m) { // NOLINT (temporary views are allowed here)
244 EXPECTS(is_matrix_square(m, true));
245
246 // nothing to do if the matrix/view is empty
247 if (m.empty()) return;
248
249 // use optimized routines for small matrices
250 if constexpr (mem::on_host<M>) {
251 if (m.shape()[0] == 1) {
253 return;
254 }
255
256 if (m.shape()[0] == 2) {
258 return;
259 }
260
261 if (m.shape()[0] == 3) {
263 return;
264 }
265 }
266
267 // use getrf and getri from lapack for larger matrices
268 array<int, 1> ipiv(m.extent(0));
269 int info = lapack::getrf(m, ipiv); // it is ok to be in C order
270 if (info != 0) NDA_RUNTIME_ERROR << "Error in nda::inverse_in_place: Matrix is not invertible: info = " << info;
271 info = lapack::getri(m, ipiv);
272 if (info != 0) NDA_RUNTIME_ERROR << "Error in nda::inverse_in_place: Matrix is not invertible: info = " << info;
273 }
274
285 template <Matrix M>
286 auto inverse(M const &m)
287 requires(get_algebra<M> == 'M')
288 {
289 EXPECTS(is_matrix_square(m, true));
290 auto r = make_regular(m);
292 return r;
293 }
294
296
297} // namespace nda
298
299namespace nda::clef {
300
306
307} // namespace nda::clef
Provides definitions and type traits involving the different memory address spaces supported by nda.
Provides the generic class for arrays.
void swap(nda::basic_array_view< V1, R1, LP1, A1, AP1, OP1 > &a, nda::basic_array_view< V2, R2, LP2, A2, AP2, OP2 > &b)=delete
std::swap is deleted for nda::basic_array_view.
Provides basic functions to create and manipulate arrays and views.
A generic multi-dimensional array.
Provides concepts for the nda library.
Provides a custom runtime error class and macros to assert conditions and throw exceptions.
Provides a generic interface to the LAPACK getrf routine.
Provides a generic interface to the LAPACK getri routine.
decltype(auto) make_regular(A &&a)
Make a given object regular.
ArrayOfRank< 1 > auto diagonal(M &m)
Get a view of the diagonal of a 2-dimensional array/view.
ArrayOfRank< 2 > auto diag(V const &v)
Get a new nda::matrix with the given values on the diagonal.
basic_array< ValueType, Rank, Layout, 'A', ContainerPolicy > array
Alias template of an nda::basic_array with an 'A' algebra.
constexpr char get_algebra
Constexpr variable that specifies the algebra of a type.
Definition traits.hpp:115
constexpr bool is_matrix_or_view_v
Constexpr variable that is true if type A is a regular matrix or a view of a matrix.
Definition traits.hpp:156
std::decay_t< decltype(get_first_element(std::declval< A const >()))> get_value_t
Get the value type of an array/view or a scalar type.
Definition traits.hpp:181
#define CLEF_MAKE_FNT_LAZY(name)
Macro to make any function lazy, i.e. accept lazy arguments and return a function call expression nod...
Definition make_lazy.hpp:89
int getri(A &&a, IPIV const &ipiv)
Interface to the LAPACK getri routine.
Definition getri.hpp:48
int getrf(A &&a, IPIV &&ipiv)
Interface to the LAPACK getrf routine.
Definition getrf.hpp:51
auto determinant_in_place(M &m)
Compute the determinant of a square matrix/view.
void inverse3_in_place(M &&m)
Compute the inverse of a 3-by-3 matrix.
auto determinant(A &&...__a)
Lazy version of nda::determinant.
void inverse1_in_place(M &&m)
Compute the inverse of a 1-by-1 matrix.
void inverse2_in_place(M &&m)
Compute the inverse of a 2-by-2 matrix.
auto determinant(M const &m)
Compute the determinant of a square matrix/view.
auto inverse(M const &m)
Compute the inverse of an n-by-n matrix.
void inverse_in_place(M &&m)
Compute the inverse of an n-by-n matrix.
bool is_matrix_diagonal(A const &a, bool print_error=false)
Check if a given array/view is diagonal, i.e. if it is square (see nda::is_matrix_square) and all the...
bool is_matrix_square(A const &a, bool print_error=false)
Check if a given array/view is square, i.e. if the first dimension has the same extent as the second ...
static constexpr bool on_host
Constexpr variable that is true if all given types have a Host address space.
Provides definitions of various layout policies.
Provides functionality to make objects, functions and methods lazy.
Provides functions to create and manipulate matrices, i.e. arrays/view with 'M' algebra.
Defines various memory handling policies.
Provides various overloads of the operator<< for nda related objects.
Contiguous layout policy with C-order (row-major order).
Definition policies.hpp:36
Memory policy using an nda::mem::handle_sso.
Definition policies.hpp:59
Provides type traits for the nda library.