OGS
LocalLinearLeastSquaresExtrapolator.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
5
6#include <Eigen/SVD>
7
8#include "BaseLib/Logging.h"
15
16namespace NumLib
17{
19 NumLib::LocalToGlobalIndexMap const& dof_table)
21{
22 /* Note in case the following assertion fails:
23 * If you copied the extrapolation code, for your processes from
24 * somewhere, note that the code from the groundwater flow process might
25 * not suit your needs: It is a special case and is therefore most
26 * likely too simplistic. You better adapt the extrapolation code from
27 * some more advanced process, like the TES process.
28 */
29 if (dof_table.getNumberOfGlobalComponents() != 1)
30 {
31 OGS_FATAL(
32 "The d.o.f. table passed must be for one variable that has only "
33 "one component!");
34 }
35}
36
38 const unsigned num_components,
39 ExtrapolatableElementCollection const& extrapolatables,
40 const double t,
41 std::vector<GlobalVector*> const& x,
42 std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table)
43{
44 auto const num_nodal_dof_result =
45 _dof_table_single_component.dofSizeWithoutGhosts() * num_components;
46
47 std::vector<GlobalIndexType> ghost_indices;
48 { // Create num_components times version of ghost_indices arranged by
49 // location. For example for 3 components and ghost_indices {5,6,10} we
50 // compute {15, 16, 17, 18, 19, 20, 30, 31, 32}.
51 auto const& single_component_ghost_indices =
52 _dof_table_single_component.getGhostIndices();
53 auto const single_component_ghost_indices_size =
54 single_component_ghost_indices.size();
55 ghost_indices.reserve(single_component_ghost_indices_size *
56 num_components);
57 for (unsigned i = 0; i < single_component_ghost_indices_size; ++i)
58 {
59 for (unsigned c = 0; c < num_components; ++c)
60 {
61 ghost_indices.push_back(
62 single_component_ghost_indices[i] * num_components + c);
63 }
64 }
65 }
66
67 if (!_nodal_values ||
68#ifdef USE_PETSC
69 _nodal_values->getLocalSize() + _nodal_values->getGhostSize()
70#else
71 _nodal_values->size()
72#endif
73 != static_cast<GlobalIndexType>(num_nodal_dof_result))
74 {
76 {num_nodal_dof_result, num_nodal_dof_result, &ghost_indices,
77 nullptr});
78 }
79 _nodal_values->setZero();
80
81 // counts the writes to each nodal value, i.e., the summands in order to
82 // compute the average afterwards
83 auto counts =
85 counts->setZero();
86
87 auto const size = extrapolatables.size();
88 for (std::size_t i = 0; i < size; ++i)
89 {
90 extrapolateElement(i, num_components, extrapolatables, t, x, dof_table,
91 *counts);
92 }
95
97 *counts);
98}
99
101 const unsigned num_components,
102 ExtrapolatableElementCollection const& extrapolatables,
103 const double t,
104 std::vector<GlobalVector*> const& x,
105 std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table)
106{
107 auto const num_element_dof_result = static_cast<GlobalIndexType>(
108 _dof_table_single_component.size() * num_components);
109
110 if (!_residuals || _residuals->size() != num_element_dof_result)
111 {
112#ifndef USE_PETSC
113 _residuals.reset(new GlobalVector{num_element_dof_result});
114#else
115 _residuals.reset(new GlobalVector{num_element_dof_result, false});
116#endif
117 }
118
119 if (static_cast<std::size_t>(num_element_dof_result) !=
120 extrapolatables.size() * num_components)
121 {
122 OGS_FATAL("mismatch in number of D.o.F.");
123 }
124
125 auto const size = extrapolatables.size();
126 for (std::size_t i = 0; i < size; ++i)
127 {
128 calculateResidualElement(i, num_components, extrapolatables, t, x,
129 dof_table);
130 }
132}
133
135 std::size_t const element_index,
136 const unsigned num_components,
137 ExtrapolatableElementCollection const& extrapolatables,
138 const double t,
139 std::vector<GlobalVector*> const& x,
140 std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table,
141 GlobalVector& counts)
142{
143 auto const& integration_point_values =
144 extrapolatables.getIntegrationPointValues(
145 element_index, t, x, dof_table, _integration_point_values_cache);
146
147 // Empty vector means to ignore the values and not to change the counts.
148 if (integration_point_values.empty())
149 {
150 return;
151 }
152
153 auto const& N_0 = extrapolatables.getShapeMatrix(element_index, 0);
154 auto const num_nodes = static_cast<unsigned>(N_0.cols());
155 auto const num_values =
156 static_cast<unsigned>(integration_point_values.size());
157
158 if (num_values % num_components != 0)
159 {
160 OGS_FATAL(
161 "The number of computed integration point values is not divisible "
162 "by the number of num_components. Maybe the computed property is "
163 "not a {:d}-component vector for each integration point.",
164 num_components);
165 }
166
167 // number of integration points in the element
168 const auto num_int_pts = num_values / num_components;
169
170 if (num_int_pts < num_nodes)
171 {
172 OGS_FATAL(
173 "Least squares is not possible if there are more nodes than "
174 "integration points.");
175 }
176
177 auto const pair_it_inserted = _qr_decomposition_cache.emplace(
178 std::make_pair(num_nodes, num_int_pts), CachedData{});
179
180 auto& cached_data = pair_it_inserted.first->second;
181 if (pair_it_inserted.second)
182 {
183 DBUG("Computing new singular value decomposition");
184
185 // interpolation_matrix * nodal_values = integration_point_values
186 // We are going to pseudo-invert this relation now using singular value
187 // decomposition.
188 auto& interpolation_matrix = cached_data.A;
189 interpolation_matrix.resize(num_int_pts, num_nodes);
190
191 interpolation_matrix.row(0) = N_0;
192 for (unsigned int_pt = 1; int_pt < num_int_pts; ++int_pt)
193 {
194 auto const& shp_mat =
195 extrapolatables.getShapeMatrix(element_index, int_pt);
196 assert(shp_mat.cols() == num_nodes);
197
198 // copy shape matrix to extrapolation matrix row-wise
199 interpolation_matrix.row(int_pt) = shp_mat;
200 }
201
202 // JacobiSVD is extremely reliable, but fast only for small matrices.
203 // But we usually have small matrices and we don't compute very often.
204 // Cf.
205 // http://eigen.tuxfamily.org/dox/group__TopicLinearAlgebraDecompositions.html
206 //
207 // Decomposes interpolation_matrix = U S V^T.
208 Eigen::JacobiSVD<Eigen::MatrixXd> svd(
209 interpolation_matrix, Eigen::ComputeThinU | Eigen::ComputeThinV);
210
211 auto const& S = svd.singularValues();
212 auto const& U = svd.matrixU();
213 auto const& V = svd.matrixV();
214
215 // Compute and save the pseudo inverse V * S^{-1} * U^T.
216 auto const rank = svd.rank();
217 assert(rank == num_nodes);
218
219 // cf. http://eigen.tuxfamily.org/dox/JacobiSVD_8h_source.html
220 cached_data.A_pinv.noalias() = V.leftCols(rank) *
221 S.head(rank).asDiagonal().inverse() *
222 U.leftCols(rank).transpose();
223 }
224 else if (cached_data.A.row(0) != N_0)
225 {
226 OGS_FATAL("The cached and the passed shapematrices differ.");
227 }
228
229 auto const& global_indices =
230 _dof_table_single_component(element_index, 0).rows;
231
232 if (num_components == 1)
233 {
234 auto const integration_point_values_vec =
235 MathLib::toVector(integration_point_values);
236
237 // Apply the pre-computed pseudo-inverse.
238 Eigen::VectorXd const nodal_values =
239 cached_data.A_pinv * integration_point_values_vec;
240
241 // TODO does that give rise to PETSc problems? E.g., writing to ghost
242 // nodes? Furthermore: Is ghost nodes communication necessary for PETSc?
243 _nodal_values->add(global_indices, nodal_values);
244 counts.add(global_indices,
245 std::vector<double>(global_indices.size(), 1.0));
246 }
247 else
248 {
249 auto const integration_point_values_mat = MathLib::toMatrix(
250 integration_point_values, num_components, num_int_pts);
251
252 // Apply the pre-computed pseudo-inverse.
253 Eigen::MatrixXd const nodal_values =
254 cached_data.A_pinv * integration_point_values_mat.transpose();
255
256 std::vector<GlobalIndexType> indices;
257 indices.reserve(num_components * global_indices.size());
258
259 // _nodal_values is ordered location-wise
260 for (unsigned comp = 0; comp < num_components; ++comp)
261 {
262 transform(cbegin(global_indices), cend(global_indices),
263 back_inserter(indices),
264 [&](auto const i) { return num_components * i + comp; });
265 }
266
267 // Nodal_values are passed as a raw pointer, because PETScVector and
268 // EigenVector implementations differ slightly.
269 _nodal_values->add(indices, nodal_values.data());
270 counts.add(indices, std::vector<double>(indices.size(), 1.0));
271 }
272}
273
275 std::size_t const element_index,
276 const unsigned num_components,
277 ExtrapolatableElementCollection const& extrapolatables,
278 const double t,
279 std::vector<GlobalVector*> const& x,
280 std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table)
281{
282 auto const& int_pt_vals = extrapolatables.getIntegrationPointValues(
283 element_index, t, x, dof_table, _integration_point_values_cache);
284
285 auto const num_values = static_cast<unsigned>(int_pt_vals.size());
286 if (num_values % num_components != 0)
287 {
288 OGS_FATAL(
289 "The number of computed integration point values is not divisible "
290 "by the number of num_components. Maybe the computed property is "
291 "not a {:d}-component vector for each integration point.",
292 num_components);
293 }
294
295 // number of integration points in the element
296 const auto num_int_pts = num_values / num_components;
297
298 const auto& global_indices =
299 _dof_table_single_component(element_index, 0).rows;
300 const auto num_nodes = static_cast<unsigned>(global_indices.size());
301
302 auto const& interpolation_matrix =
303 _qr_decomposition_cache.find({num_nodes, num_int_pts})->second.A;
304
305 Eigen::VectorXd nodal_vals_element(num_nodes);
306 auto const int_pt_vals_mat =
307 MathLib::toMatrix(int_pt_vals, num_components, num_int_pts);
308
310 *_nodal_values); // For access in the for-loop.
311 for (unsigned comp = 0; comp < num_components; ++comp)
312 {
313 // filter nodal values of the current element
314 for (unsigned i = 0; i < num_nodes; ++i)
315 {
316 // TODO PETSc negative indices?
317 auto const idx = num_components * global_indices[i] + comp;
318 nodal_vals_element[i] = _nodal_values->get(idx);
319 }
320
321 double const residual = (interpolation_matrix * nodal_vals_element -
322 int_pt_vals_mat.row(comp).transpose())
323 .squaredNorm();
324
325 auto const eidx =
326 static_cast<GlobalIndexType>(num_components * element_index + comp);
327 // The residual is set to the root mean square value.
328 auto const root_mean_square = std::sqrt(residual / num_int_pts);
329 _residuals->set(eidx, root_mean_square);
330 }
331}
332
333} // namespace NumLib
#define OGS_FATAL(...)
Definition Error.h:19
MathLib::EigenVector GlobalVector
GlobalMatrix::IndexType GlobalIndexType
void DBUG(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:22
void add(IndexType rowId, double v)
add entry
Definition EigenVector.h:70
virtual Eigen::Map< const Eigen::RowVectorXd > getShapeMatrix(std::size_t const id, unsigned const integration_point) const =0
virtual std::size_t size() const =0
Returns the number of elements whose properties shall be extrapolated.
virtual std::vector< double > const & getIntegrationPointValues(std::size_t const id, const double t, std::vector< GlobalVector * > const &x, std::vector< NumLib::LocalToGlobalIndexMap const * > const &dof_table, std::vector< double > &cache) const =0
void calculateResidualElement(std::size_t const element_index, const unsigned num_components, ExtrapolatableElementCollection const &extrapolatables, const double t, std::vector< GlobalVector * > const &x, std::vector< NumLib::LocalToGlobalIndexMap const * > const &dof_table)
Compute the residuals for one element.
void extrapolateElement(std::size_t const element_index, const unsigned num_components, ExtrapolatableElementCollection const &extrapolatables, const double t, std::vector< GlobalVector * > const &x, std::vector< NumLib::LocalToGlobalIndexMap const * > const &dof_table, GlobalVector &counts)
Extrapolate one element.
LocalLinearLeastSquaresExtrapolator(NumLib::LocalToGlobalIndexMap const &dof_table)
NumLib::LocalToGlobalIndexMap const & _dof_table_single_component
DOF table used for writing to global vectors.
void extrapolate(const unsigned num_components, ExtrapolatableElementCollection const &extrapolatables, const double t, std::vector< GlobalVector * > const &x, std::vector< NumLib::LocalToGlobalIndexMap const * > const &dof_table) override
Extrapolates the given property from the given local assemblers.
std::unique_ptr< GlobalVector > _nodal_values
extrapolated nodal values
void calculateResiduals(const unsigned num_components, ExtrapolatableElementCollection const &extrapolatables, const double t, std::vector< GlobalVector * > const &x, std::vector< NumLib::LocalToGlobalIndexMap const * > const &dof_table) override
std::unique_ptr< GlobalVector > _residuals
extrapolation residuals
std::vector< double > _integration_point_values_cache
Avoids frequent reallocations.
std::map< std::pair< unsigned, unsigned >, CachedData > _qr_decomposition_cache
void finalizeAssembly(PETScMatrix &A)
Definition LinAlg.cpp:191
void componentwiseDivide(PETScVector &w, PETScVector const &x, PETScVector const &y)
Definition LinAlg.cpp:74
void setLocalAccessibleVector(PETScVector const &x)
Definition LinAlg.cpp:20
Eigen::Map< const Vector > toVector(std::vector< double > const &data, Eigen::VectorXd::Index size)
Creates an Eigen mapped vector from the given data vector.
Eigen::Map< const Matrix > toMatrix(std::vector< double > const &data, Eigen::MatrixXd::Index rows, Eigen::MatrixXd::Index cols)
Stores a matrix and its Moore-Penrose pseudo-inverse.