chombo-discharge
Loading...
Searching...
No Matches
CD_LeastSquaresImplem.H
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2021-2026 SINTEF Energy Research
3 *
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
13#ifndef CD_LEASTSQUARESIMPLEM_H
14#define CD_LEASTSQUARESIMPLEM_H
15
16// Our includes
17#include <CD_LaPackUtils.H>
18#include <CD_LeastSquares.H>
19#include <CD_MultiIndex.H>
20#include <CD_NamespaceHeader.H>
21
22inline Vector<Real>
23LeastSquares::makeDiagWeights(const Vector<RealVect>& a_displacements, const int a_pow)
24{
25 Vector<Real> ret(a_displacements.size(), 1.0);
26
27 if (a_pow > 0) {
28 // Figure out the min distance, and normalize every weight with this distance. We do not use physical
29 // distances when weighting the equations :)
30 Real normDist = 0.0;
31 for (int i = 0; i < a_displacements.size(); i++) {
32 normDist = std::max(normDist, a_displacements[i].vectorLength());
33 }
34
35 // Set weightings = 1/(1 + |displacement|/normDist)^a_pow
36 for (int i = 0; i < a_displacements.size(); i++) {
37 const Real len = a_displacements[i].vectorLength() / normDist;
38 const Real w = std::pow(1 + len, -a_pow);
39
40 ret[i] = w;
41 }
42 }
43
44 return ret;
45}
46
47template <typename T>
48std::map<IntVect, std::pair<VoFStencil, VoFStencil>>
49LeastSquares::computeDualLevelStencils(const IntVectSet& a_derivs,
50 const IntVectSet& a_knownTerms,
51 const Vector<VolIndex>& a_fineVofs,
52 const Vector<VolIndex>& a_coarVofs,
53 const Vector<RealVect>& a_fineDisplacements,
54 const Vector<RealVect>& a_coarDisplacements,
55 const int a_p,
56 const int a_order)
57{
58 const Vector<Real> fineWeights = LeastSquares::makeDiagWeights(a_fineDisplacements, a_p);
59 const Vector<Real> coarWeights = LeastSquares::makeDiagWeights(a_coarDisplacements, a_p);
60
61 return LeastSquares::computeDualLevelStencils<T>(a_derivs,
62 a_knownTerms,
63 a_fineVofs,
64 a_coarVofs,
65 a_fineDisplacements,
66 a_coarDisplacements,
67 fineWeights,
68 coarWeights,
69 a_order);
70}
71
72template <typename T>
73std::map<IntVect, std::pair<VoFStencil, VoFStencil>>
74LeastSquares::computeDualLevelStencils(const IntVectSet& a_derivs,
75 const IntVectSet& a_knownTerms,
76 const Vector<VolIndex>& a_fineVofs,
77 const Vector<VolIndex>& a_coarVofs,
78 const Vector<RealVect>& a_fineDisplacements,
79 const Vector<RealVect>& a_coarDisplacements,
80 const Vector<Real>& a_fineWeights,
81 const Vector<Real>& a_coarWeights,
82 const int a_order)
83{
84 // TLDR: This routine does a two-level least squares solve of a system of equations, each equation describing
85 // a Taylor series expansion up to specified order (a_order). This routine "solves" this overdetermined system
86 // using weighted least squares so that we can obtain terms in the Taylor series. The user will have specified
87 // the desired unknowns (a_derivs) to be returned in stencil form as well as any terms that were already
88 // known (a_knownTerms).
89 //
90 // The least squares solve is done by LaPack, and so the least squares system matrix is filled in Fortran
91 // order. The actual "solve" consists of using the Moore-Penrose pseudoinverse and once we have inverted
92 // the system we can form stencils for each of the specified terms in the Taylor series.
93 //
94 // This routine does not contain much code, but can be difficult to understand.
95
96 CH_assert(a_order > 0);
97 CH_assert(a_fineVofs.size() == a_fineDisplacements.size());
98 CH_assert(a_coarVofs.size() == a_coarDisplacements.size());
99 CH_assert(a_fineVofs.size() == a_fineWeights.size());
100 CH_assert(a_coarVofs.size() == a_coarWeights.size());
101
102 // Initialize return stuff
103 std::map<IntVect, std::pair<VoFStencil, VoFStencil>> ret;
104 for (IVSIterator ivsIt(a_derivs); ivsIt.ok(); ++ivsIt) {
105 ret.emplace(ivsIt(), std::make_pair(VoFStencil(), VoFStencil()));
106 }
107
108 if (a_derivs.numPts() > 0) {
109
110 // This is because some unknowns (rows) can be eliminated.
111 const int M = LeastSquares::getTaylorExpansionSize(a_order) - a_knownTerms.numPts();
112 const int Kfine = a_fineDisplacements.size();
113 const int Kcoar = a_coarDisplacements.size();
114 const int K = Kfine + Kcoar;
115
116 const IntVectSet isect = a_derivs & a_knownTerms;
117
118 if (K < M) {
119 MayDay::Abort("LeastSquares::computeDualLevelStencils -- not enough equations to achieve desired order!");
120 }
121 if (!isect.isEmpty()) {
122 MayDay::Abort(
123 "LeastSquares::computeDualLevelStencils - you have specified the same terms as both unknown and known");
124 }
125
126 // Build the A-matrix in column major order (this is what Fortran wants) so we can use
127 // LaPackUtils::computePseudoInverse.
128 // ----------------------------------------------------------------------------------------------------------------------
129 // If we have an (unweighted) full system then our system A*x = b is
130 //
131 // A x b
132 // === === ===
133 // [1 (x-x0) 0.5(x-x0)^2 ...] [f(x) ] [f(x0)]
134 // [1 (x-x1) 0.5(x-x1)^2 ...] [df/dx ] [f(x1)]
135 // [1 (x-x2) 0.5(x-x2)^2 ...] [d^2f/dx^2](x) = [f(x2)]
136 // [: : : ] [ : ] [ : ]
137 // [: : : ] [ : ] [ : ]
138 //
139 // Extensions to 2D/3D simply use multi-index notation, and weights are simply multiplied into each row, i.e. we
140 // solve (w*A) * x = (w*b). Inverting the system gives x = [w * A^+ * w] * b where A^+ is the Moore-Penrose
141 // pseudoinverse. We put the result of [w * A^+ * w] into a stencil.
142 //
143 // Note that columns can be eliminated through knownTerms, in which case we remove unknowns (i.e., rows) from the
144 // system. This will also correspond to a modification of the right-hand side, but the required modifications are
145 // not accessible in this routine, and so the user will have to make sense of them.
146
147 int i = 0; // Exists just because we fill memory linearly.
148 Vector<T> linA(K * M, 0.0); // Equal to (w*A)
149 Vector<T> linAplus(M * K, 0.0); // Equal to (w*A)^+
150
151 // Loop over column
152 for (MultiIndex mi(a_order); mi.ok(); ++mi) {
153 if (!a_knownTerms.contains(mi.getCurrentIndex())) {
154
155 // Fill column, write the system using the fine vofs first, then the coarse vofs.
156 for (int k = 0; k < K; k++) {
157 if (k < Kfine) {
158 linA[i] = a_fineWeights[k] * mi.pow(a_fineDisplacements[k]) / mi.factorial();
159 }
160 else {
161 linA[i] = a_coarWeights[k - Kfine] * mi.pow(a_coarDisplacements[k - Kfine]) / mi.factorial();
162 }
163 i++;
164 }
165 }
166 }
167
168 // Compute the pseudo-inverse.
169 const bool foundSVD = LaPackUtils::computePseudoInverse(linAplus.stdVector(), linA.stdVector(), K, M);
170
171 if (foundSVD) {
172 // When we have eliminated rows in the linear system we can't use MultiIndex to map directly, this occurs because
173 // if we eliminated unknowns (rows) our system can be something like (if we eliminated term f(x)):
174 //
175 // [(x-x0) 0.5(x-x0)^2 ...] [df/dx ] [f(x0) - f(x)]
176 // [(x-x1) 0.5(x-x1)^2 ...] [d^2f/dx^2] [f(x1) - f(x)]
177 // [(x-x2) 0.5(x-x2)^2 ...] [ ](x) = [f(x2) - f(x)]
178 // [ : : ] [ : ] [ : ]
179 // [ : : ] [ : ] [ : ]
180 //
181 // The MultiIndex won't know about this, and will (correctly!), believe that the first row corresponds to
182 // multi-index (0,0,0). Since that is truly not the case, we map the rows in A to multi-indices and use that to
183 // identify the row in (w*A)^+ that corresponds to a specific unknown in the Taylor series. This is what happens
184 // below.
185 std::map<IntVect, int> rowMap;
186 int row = 0;
187 for (MultiIndex mi(a_order); mi.ok(); ++mi) {
188
189 // This is the order in which A was built.
190 if (!a_knownTerms.contains(mi.getCurrentIndex())) {
191 rowMap.emplace(mi.getCurrentIndex(), row);
192 row++;
193 }
194 }
195
196 // Recall that linAplus is M*K so the stride is always M, starting at some specified row.
197 for (IVSIterator ivsIt(a_derivs); ivsIt.ok(); ++ivsIt) {
198 const IntVect deriv = ivsIt();
199
200 if (rowMap.find(ivsIt()) != rowMap.end()) {
201 row = rowMap.at(ivsIt());
202 }
203 else {
204 MayDay::Error("LeastSquares::computeDualLevelStencils -- map is out of range but this shouldn't happen!");
205 }
206
207 std::pair<VoFStencil, VoFStencil>& sten = ret.at(deriv);
208
209 sten.first.clear();
210 sten.second.clear();
211
212 // Map the pseudoinverse into something that is usable by a stencil. Note that linAplus is (w*A)^+, but we want
213 // the term [(w*A)^+ * w], so we also need to multiply in the weights here (because the right-hand side was also
214 // weighted).
215 for (int k = 0; k < K; k++) {
216 const int idx = row + k * M;
217 if (k < Kfine) {
218 sten.first.add(a_fineVofs[k], a_fineWeights[k] * linAplus[idx]);
219 }
220 else {
221 sten.second.add(a_coarVofs[k - Kfine], a_coarWeights[k - Kfine] * linAplus[idx]);
222 }
223 }
224 }
225 }
226 else {
227 MayDay::Warning("LeastSquares::computeDualLevelStencils - could not perform singular value decomposition");
228 }
229 }
230
231 return ret;
232}
233
234#include <CD_NamespaceFooter.H>
235
236#endif
Interface to some LaPack routines.
Agglomeration of various routines that use least squares computations.
Declaration of a SpaceDim multi-index type for use with higher order Taylor series.
static Vector< Real > makeDiagWeights(const Vector< RealVect > &a_displacements, int a_pow)
Create a list of weights. This routine returns a list of diagonal weights for a least squares system....
Definition CD_LeastSquaresImplem.H:23
static std::map< IntVect, std::pair< VoFStencil, VoFStencil > > computeDualLevelStencils(const IntVectSet &a_derivs, const IntVectSet &a_knownTerms, const Vector< VolIndex > &a_fineVofs, const Vector< VolIndex > &a_coarVofs, const Vector< RealVect > &a_fineDisplacements, const Vector< RealVect > &a_coarDisplacements, int a_p, int a_order)
Compute a least squares interpolation to a specified order. This version separates the stencils into ...
Definition CD_LeastSquaresImplem.H:49
static int getTaylorExpansionSize(int a_order)
Get the size of a Taylor expansion for a given order.
Definition CD_LeastSquares.cpp:420
SpaceDim multi-index type for use with higher order Taylor series.
Definition CD_MultiIndex.H:32
bool ok() const
Check that multi-index is ok.
Definition CD_MultiIndex.cpp:94
bool computePseudoInverse(std::vector< double > &a_linAplus, const std::vector< double > &a_linA, const int &a_M, const int &a_N)
Compute the pseudoinverse of matrix through singular value decomposition.
Definition CD_LaPackUtils.cpp:164