Skip to content

Commit 1c9ff1a

Browse files
authored
Merge pull request arborx#1128 from aprokop/sym_svd
2 parents 7c4ff52 + d398b6d commit 1c9ff1a

File tree

4 files changed

+72
-78
lines changed

4 files changed

+72
-78
lines changed

src/interpolation/detail/ArborX_InterpDetailsMovingLeastSquaresCoefficients.hpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
#include <detail/ArborX_AccessTraits.hpp>
1818
#include <detail/ArborX_InterpDetailsCompactRadialBasisFunction.hpp>
1919
#include <detail/ArborX_InterpDetailsPolynomialBasis.hpp>
20-
#include <detail/ArborX_InterpDetailsSymmetricPseudoInverseSVD.hpp>
2120
#include <kokkos_ext/ArborX_KokkosExtAccessibilityTraits.hpp>
21+
#include <misc/ArborX_SymmetricSVD.hpp>
2222

2323
#include <Kokkos_Core.hpp>
2424
#include <Kokkos_Profiling_ScopedRegion.hpp>
@@ -110,7 +110,8 @@ class MovingLeastSquaresCoefficientsKernel
110110

111111
// We need the inverse of P^T.PHI.P, and because it is symmetric, we can use
112112
// the symmetric SVD algorithm to get it.
113-
symmetricPseudoInverseSVDKernel(moment, svd_diag, svd_unit);
113+
::ArborX::Details::symmetricPseudoInverseSVDKernel(moment, svd_diag,
114+
svd_unit);
114115
// Now, the moment has [P^T.PHI.P]^-1
115116

116117
// Finally, the result is produced by computing p(0).[P^T.PHI.P]^-1.P^T.PHI

src/interpolation/detail/ArborX_InterpDetailsSymmetricPseudoInverseSVD.hpp src/misc/ArborX_SymmetricSVD.hpp

+31-54
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@
99
* SPDX-License-Identifier: BSD-3-Clause *
1010
****************************************************************************/
1111

12-
#ifndef ARBORX_INTERP_SYMMETRIC_PSEUDO_INVERSE_SVD_HPP
13-
#define ARBORX_INTERP_SYMMETRIC_PSEUDO_INVERSE_SVD_HPP
12+
#ifndef ARBORX_SYMMETRIC_SVD_HPP
13+
#define ARBORX_SYMMETRIC_SVD_HPP
1414

1515
#include <kokkos_ext/ArborX_KokkosExtAccessibilityTraits.hpp>
1616
#include <misc/ArborX_Exception.hpp>
1717

1818
#include <Kokkos_Core.hpp>
1919
#include <Kokkos_Profiling_ScopedRegion.hpp>
2020

21-
namespace ArborX::Interpolation::Details
21+
namespace ArborX::Details
2222
{
2323

2424
template <typename Matrix>
@@ -77,18 +77,17 @@ KOKKOS_FUNCTION auto argmaxUpperTriangle(Matrix const &mat)
7777
return result;
7878
}
7979

80-
// Pseudo-inverse of symmetric matrices using SVD
80+
// SVD of a symmetric matrix
8181
// We must find U, E (diagonal and positive) and V such that A = U.E.V^T
8282
// We also suppose, as the input, that A is symmetric, so U = SV where S is
8383
// a sign matrix (only 1 or -1 on the diagonal, 0 elsewhere).
84-
// Thus A = U.ES.U^T and A^-1 = U.[ ES^-1 ].U^T
84+
// Thus A = U.ES.U^T.
8585
//
8686
// mat <=> initial ES
8787
// diag <=> final ES
8888
// unit <=> U
8989
template <typename Matrix, typename Diag, typename Unit>
90-
KOKKOS_FUNCTION void symmetricPseudoInverseSVDKernel(Matrix &mat, Diag &diag,
91-
Unit &unit)
90+
KOKKOS_FUNCTION void symmetricSVDKernel(Matrix &mat, Diag &diag, Unit &unit)
9291
{
9392
ensureIsSquareSymmetricMatrix(mat);
9493
static_assert(!std::is_const_v<typename Matrix::value_type>,
@@ -203,13 +202,34 @@ KOKKOS_FUNCTION void symmetricPseudoInverseSVDKernel(Matrix &mat, Diag &diag,
203202
}
204203
}
205204

205+
for (int i = 0; i < size; i++)
206+
diag(i) = mat(i, i);
207+
}
208+
209+
// Pseudo-inverse of symmetric matrices using SVD
210+
// We must find U, E (diagonal and positive) and V such that A = U.E.V^T
211+
// We also suppose, as the input, that A is symmetric, so U = SV where S is
212+
// a sign matrix (only 1 or -1 on the diagonal, 0 elsewhere).
213+
// Thus A = U.ES.U^T and A^-1 = U.[ ES^-1 ].U^T
214+
//
215+
// mat <=> initial ES
216+
// diag <=> final ES
217+
// unit <=> U
218+
template <typename Matrix, typename Diag, typename Unit>
219+
KOKKOS_FUNCTION void symmetricPseudoInverseSVDKernel(Matrix &mat, Diag &diag,
220+
Unit &unit)
221+
{
222+
symmetricSVDKernel(mat, diag, unit);
223+
224+
int const size = mat.extent(0);
225+
226+
using Value = typename Matrix::non_const_value_type;
227+
constexpr Value epsilon = Kokkos::Experimental::epsilon_v<float>;
228+
206229
// We compute the max to get a range of the invertible eigenvalues
207230
auto max_eigen = epsilon;
208231
for (int i = 0; i < size; i++)
209-
{
210-
diag(i) = mat(i, i);
211232
max_eigen = Kokkos::max(Kokkos::abs(diag(i)), max_eigen);
212-
}
213233
auto const threshold = max_eigen * epsilon;
214234

215235
// We invert the diagonal of 'mat', except if "0" is found
@@ -226,49 +246,6 @@ KOKKOS_FUNCTION void symmetricPseudoInverseSVDKernel(Matrix &mat, Diag &diag,
226246
}
227247
}
228248

229-
template <typename ExecutionSpace, typename InOutMatrices>
230-
void symmetricPseudoInverseSVD(ExecutionSpace const &space,
231-
InOutMatrices &matrices)
232-
{
233-
auto guard =
234-
Kokkos::Profiling::ScopedRegion("ArborX::SymmetricPseudoInverseSVD");
235-
236-
// InOutMatrices is a list of square symmetric matrices (3D view)
237-
static_assert(Kokkos::is_view_v<InOutMatrices>, "matrices must be a view");
238-
static_assert(!std::is_const_v<typename InOutMatrices::value_type>,
239-
"matrices must be writable");
240-
static_assert(InOutMatrices::rank() == 3,
241-
"matrices must be a list of square matrices");
242-
static_assert(
243-
ArborX::Details::KokkosExt::is_accessible_from<
244-
typename InOutMatrices::memory_space, ExecutionSpace>::value,
245-
"matrices must be accessible from the execution space");
246-
247-
KOKKOS_ASSERT(matrices.extent(1) == matrices.extent(2)); // Must be square
248-
249-
using Value = typename InOutMatrices::non_const_value_type;
250-
using MemorySpace = typename InOutMatrices::memory_space;
251-
252-
Kokkos::View<Value **, MemorySpace> diags(
253-
Kokkos::view_alloc(space, Kokkos::WithoutInitializing,
254-
"ArborX::SymmetricPseudoInverseSVD::diags"),
255-
matrices.extent(0), matrices.extent(1));
256-
Kokkos::View<Value ***, MemorySpace> units(
257-
Kokkos::view_alloc(space, Kokkos::WithoutInitializing,
258-
"ArborX::SymmetricPseudoInverseSVD::units"),
259-
matrices.extent(0), matrices.extent(1), matrices.extent(2));
260-
261-
Kokkos::parallel_for(
262-
"ArborX::SymmetricPseudoInverseSVD::computations",
263-
Kokkos::RangePolicy(space, 0, matrices.extent(0)),
264-
KOKKOS_LAMBDA(int const i) {
265-
auto mat = Kokkos::subview(matrices, i, Kokkos::ALL, Kokkos::ALL);
266-
auto diag = Kokkos::subview(diags, i, Kokkos::ALL);
267-
auto unit = Kokkos::subview(units, i, Kokkos::ALL, Kokkos::ALL);
268-
symmetricPseudoInverseSVDKernel(mat, diag, unit);
269-
});
270-
}
271-
272-
} // namespace ArborX::Interpolation::Details
249+
} // namespace ArborX::Details
273250

274251
#endif

test/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ add_executable(ArborX_Test_DetailsUtils.exe
4949
tstAttachIndices.cpp
5050
tstDetailsVector.cpp
5151
tstDetailsUtils.cpp
52+
tstDetailsSVD.cpp
5253
tstDetailsGeometryReducer.cpp
5354
utf_main.cpp
5455
)
@@ -255,7 +256,6 @@ target_link_libraries(ArborX_Test_BoostAdapters.exe PRIVATE ArborX Boost::unit_t
255256
add_test(NAME ArborX_Test_BoostAdapters COMMAND ArborX_Test_BoostAdapters.exe)
256257

257258
add_executable(ArborX_Test_InterpMovingLeastSquares.exe
258-
tstInterpDetailsSVD.cpp
259259
tstInterpDetailsCompactRadialBasisFunction.cpp
260260
tstInterpDetailsPolyBasis.cpp
261261
tstInterpDetailsMLSCoefficients.cpp

test/tstInterpDetailsSVD.cpp test/tstDetailsSVD.cpp

+37-21
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,60 @@
1111

1212
#include "ArborX_EnableDeviceTypes.hpp"
1313
#include "ArborX_EnableViewComparison.hpp"
14-
#include <detail/ArborX_InterpDetailsSymmetricPseudoInverseSVD.hpp>
14+
#include <misc/ArborX_SymmetricSVD.hpp>
1515

1616
#include "BoostTest_CUDA_clang_workarounds.hpp"
1717
#include <boost/test/unit_test.hpp>
1818

19-
template <typename MS, typename ES, typename V, int M, int N>
20-
void makeCase(ES const &es, V const (&src_arr)[M][N][N],
21-
V const (&ref_arr)[M][N][N])
19+
template <typename MemorySpace, typename ExecutionSpace, typename Value, int M,
20+
int N>
21+
void makeCase(ExecutionSpace const &exec, Value const (&src_arr)[M][N][N],
22+
Value const (&ref_arr)[M][N][N])
2223
{
23-
using DeviceView = Kokkos::View<V[M][N][N], MS>;
24+
using DeviceView = Kokkos::View<Value[N][N], MemorySpace>;
2425
using HostView = typename DeviceView::HostMirror;
2526

2627
HostView src("Testing::src");
2728
HostView ref("Testing::ref");
29+
2830
DeviceView inv("Testing::inv");
31+
DeviceView unit("Testing::unit");
32+
Kokkos::View<Value[N], MemorySpace> diag("Testing::diag");
2933

3034
for (int i = 0; i < M; i++)
35+
{
3136
for (int j = 0; j < N; j++)
3237
for (int k = 0; k < N; k++)
3338
{
34-
src(i, j, k) = src_arr[i][j][k];
35-
ref(i, j, k) = ref_arr[i][j][k];
39+
src(j, k) = src_arr[i][j][k];
40+
ref(j, k) = ref_arr[i][j][k];
3641
}
3742

38-
Kokkos::deep_copy(es, inv, src);
39-
ArborX::Interpolation::Details::symmetricPseudoInverseSVD(es, inv);
40-
ARBORX_MDVIEW_TEST_TOL(ref, inv, Kokkos::Experimental::epsilon_v<float>);
43+
// Test SVD
44+
Kokkos::deep_copy(exec, inv, src);
45+
Kokkos::parallel_for(
46+
"Testing::run_svd", Kokkos::RangePolicy<ExecutionSpace>(exec, 0, 1),
47+
KOKKOS_LAMBDA(int) {
48+
ArborX::Details::symmetricSVDKernel(inv, diag, unit);
49+
for (int p = 0; p < N; ++p)
50+
for (int q = 0; q < N; ++q)
51+
{
52+
inv(p, q) = 0;
53+
for (int s = 0; s < N; ++s)
54+
inv(p, q) += unit(p, s) * diag(s) * unit(q, s);
55+
}
56+
});
57+
ARBORX_MDVIEW_TEST_TOL(src, inv, Kokkos::Experimental::epsilon_v<float>);
58+
59+
// Test pseudo-inverse through SVD
60+
Kokkos::deep_copy(exec, inv, src);
61+
Kokkos::parallel_for(
62+
"Testing::run_inverse", Kokkos::RangePolicy<ExecutionSpace>(exec, 0, 1),
63+
KOKKOS_LAMBDA(int) {
64+
ArborX::Details::symmetricPseudoInverseSVDKernel(inv, diag, unit);
65+
});
66+
ARBORX_MDVIEW_TEST_TOL(ref, inv, Kokkos::Experimental::epsilon_v<float>);
67+
}
4168
}
4269

4370
// Pseudo-inverses were computed using numpy's "linalg.pinv" solver and
@@ -115,14 +142,3 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(pseudo_inv_scalar_like, DeviceType,
115142
double inv[2][1][1] = {{{1 / 2.}}, {{0}}};
116143
makeCase<MemorySpace>(space, mat, inv);
117144
}
118-
119-
BOOST_AUTO_TEST_CASE_TEMPLATE(pseudo_inv_empty, DeviceType, ARBORX_DEVICE_TYPES)
120-
{
121-
using ExecutionSpace = typename DeviceType::execution_space;
122-
using MemorySpace = typename DeviceType::memory_space;
123-
ExecutionSpace space{};
124-
125-
Kokkos::View<double ***, MemorySpace> mat("mat", 0, 0, 0);
126-
ArborX::Interpolation::Details::symmetricPseudoInverseSVD(space, mat);
127-
BOOST_TEST(mat.size() == 0);
128-
}

0 commit comments

Comments
 (0)