Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
ApplyWeights.cpp
Go to the documentation of this file.
1 /*
2  * =====================================================================================
3  *
4  * Filename: ApplyWeights.cpp
5  *
6  * Description: Kernels to apply the sparse-matrix onto a dense vector using various
7  * optimized implementation algorithms. Internally, we store the maps
8  * using Eigen3 datastructures, but for bit-for-bit reproducibility,
9  * we also have some alternatives to experiment.
10  *
11  * Revision: none
12  *
13  * =====================================================================================
14  */
15 
17 
18 #include <algorithm>
19 #include <utility>
20 #include <vector>
21 
22 // ** Kahan Summation Algorithm for improved numerical accuracy **
23 struct KahanSum
24 {
25  double sum = 0.0;
26  double correction = 0.0;
27 
28  void add( double value )
29  {
30  double y = value - correction; // Correct the input
31  double t = sum + y; // Perform the sum
32  correction = ( t - sum ) - y; // Update correction
33  sum = t; // Store the new sum
34  }
35 
36  double result() const
37  {
38  return sum;
39  }
40 };
41 
42 ///////////////////////////////////////////////////////////////////////////////
43 
44 
45 // Pairwise summation helper function
46 inline double pairwiseSum( const std::set< double >& sorted )
47 {
48  if( sorted.empty() ) return 0.0;
49  if( sorted.size() == 1 ) return *sorted.begin();
50 
51  // Accumulate pairwise to minimize rounding error
52  double sum = 0.0;
53  for( double val : sorted )
54  sum += val;
55  return sum;
56 }
57 
58 // Pairwise summation helper function
59 inline double pairwiseKahanSum( const std::set< double >& sorted )
60 {
61  if( sorted.empty() ) return 0.0;
62  if( sorted.size() == 1 ) return *sorted.begin();
63 
64  // Accumulate pairwise to minimize rounding error
65  // Apply pairwise summation with Kahan correction
66  KahanSum kahan;
67  for( double val : sorted )
68  kahan.add( val );
69  return kahan.result();
70 }
71 
72 // Sparse matrix-vector multiplication using pairwise summation
73 inline void deterministicSparseMatVecMul( const typename moab::TempestOnlineMap::WeightMatrix& A,
74  const typename moab::TempestOnlineMap::WeightColVector& x,
75  typename moab::TempestOnlineMap::WeightRowVector& result )
76 {
77  constexpr bool useKahanSum = false;
78  constexpr bool usePairwiseSum = false;
79 
80  result.setZero(); // Ensure no uninitialized memory issues
81 
82  // Iterate row-wise to enforce a fixed summation order
83  for( int row = 0; row < A.outerSize(); ++row )
84  {
85  std::set< double > accumulators;
86  for( typename moab::TempestOnlineMap::WeightMatrix::InnerIterator it( A, row ); it; ++it )
87  {
88  // accumulators contains the sorted values of the product: A(row, col) * x(col)
89  accumulators.insert( it.value() * x( it.col() ) );
90  }
91  if( usePairwiseSum ) result( row ) = pairwiseSum( accumulators );
92  if( useKahanSum ) result( row ) = pairwiseKahanSum( accumulators );
93 
94  if( !usePairwiseSum && !useKahanSum )
95  {
96  double sum = 0.0;
97  for( double val : accumulators )
98  sum += val;
99  result( row ) = sum;
100  }
101  }
102 }
103 
104 //
105 // Perform a deterministic sparse matrix-vector multiplication
106 inline void deterministicSparseMatVecMulKahan( const typename moab::TempestOnlineMap::WeightMatrix& A,
107  const typename moab::TempestOnlineMap::WeightColVector& x,
108  typename moab::TempestOnlineMap::WeightRowVector& result )
109 {
110  result.setZero(); // Ensure no uninitialized memory issues
111 
112  // Iterate row-wise to enforce a fixed summation order
113  for( int row = 0; row < A.outerSize(); ++row )
114  {
115  KahanSum kahan;
116  for( typename moab::TempestOnlineMap::WeightMatrix::InnerIterator it( A, row ); it; ++it )
117  {
118  double product = it.value() * x( it.col() ); // Compute product
119  kahan.add( product );
120  }
121 
122  result( row ) = kahan.result();
123  }
124 }
125 
126 // Perform a deterministic sparse matrix-vector multiplication
127 inline void deterministicSparseMatVecMulClean( const typename moab::TempestOnlineMap::WeightMatrix& A,
128  const typename moab::TempestOnlineMap::WeightColVector& x,
129  typename moab::TempestOnlineMap::WeightRowVector& result )
130 {
131  result.setZero(); // Ensure no uninitialized memory issues
132 
133  // Iterate row-wise to enforce a fixed summation order
134  for( int row = 0; row < A.outerSize(); ++row )
135  {
136  for( typename moab::TempestOnlineMap::WeightMatrix::InnerIterator it( A, row ); it; ++it )
137  {
138  const double product = it.value() * x( it.col() ); // Compute product
139  result( it.row() ) += product;
140  }
141  }
142 }
143 
144 inline void deterministicSparseMatVecMulNative( const typename moab::TempestOnlineMap::WeightMatrix& A,
145  const typename moab::TempestOnlineMap::WeightColVector& x,
146  typename moab::TempestOnlineMap::WeightRowVector& result )
147 {
148  result = A * x; // Perform the matrix-vector multiplication using Eigen3
149 }
150 
151 // Deterministic sparse matrix-vector multiplication with A^T * x using pairwise summation
152 inline void deterministicSparseMatTransposeVecMul( const typename moab::TempestOnlineMap::WeightMatrix& A,
153  const typename moab::TempestOnlineMap::WeightRowVector& x,
154  typename moab::TempestOnlineMap::WeightColVector& result )
155 {
156  result.setZero(); // Ensure no uninitialized memory issues
157 
158  // Temporary storage for pairwise summation
159  std::vector< std::set< double > > accumulators( A.cols() );
160 
161  // Iterate over A row-wise, but accumulate into result as if computing A^T * x
162  for( int row = 0; row < A.outerSize(); ++row )
163  {
164  for( typename moab::TempestOnlineMap::WeightMatrix::InnerIterator it( A, row ); it; ++it )
165  {
166  accumulators[it.col()].insert( it.value() * x( row ) );
167  }
168  }
169 
170  // Compute final sum using pairwise summation for each entry
171  for( int col = 0; col < A.cols(); ++col )
172  {
173  // result( col ) = pairwiseSum( accumulators[col] );
174  result( col ) = pairwiseKahanSum( accumulators[col] );
175  }
176 }
177 
178 // Perform a deterministic sparse matrix-vector multiplication
179 inline void deterministicSparseMatTransposeVecMulClean( const typename moab::TempestOnlineMap::WeightMatrix& A,
180  const typename moab::TempestOnlineMap::WeightRowVector& x,
181  typename moab::TempestOnlineMap::WeightColVector& result )
182 {
183  result.setZero(); // Ensure no uninitialized memory issues
184 
185  // Iterate over A row-wise, but accumulate into result as if computing A^T * x
186  for( int row = 0; row < A.outerSize(); ++row )
187  {
188  for( typename moab::TempestOnlineMap::WeightMatrix::InnerIterator it( A, row ); it; ++it )
189  {
190  const double product = it.value() * x( row ); // Compute product
191  result( it.col() ) += product; // Accumulate contributions to the corresponding row in A^T
192  }
193  }
194 }
195 
196 // Perform a deterministic sparse matrix-vector multiplication
197 inline void deterministicSparseMatTransposeVecMulNative( const typename moab::TempestOnlineMap::WeightMatrix& A,
198  const typename moab::TempestOnlineMap::WeightRowVector& x,
199  typename moab::TempestOnlineMap::WeightColVector& result )
200 {
201  result = A.adjoint() * x; // Perform the adjoint.matrix-vector multiplication using Eigen3
202 }
203 ///////////////////////////////////////////////////////////////////////////////
204 
206  std::vector< double >& tgtVals,
207  bool transpose )
208 {
209  // Reset the source and target data first
210  m_rowVector.setZero();
211  m_colVector.setZero();
212 
213 #ifdef VERBOSE
214  std::stringstream sstr;
215  static int callId = 0;
216  callId++;
217  sstr << "projection_id_" << callId << "_s_" << size << "_rk_" << rank << ".txt";
218  std::ofstream output_file( sstr.str() );
219 #endif
220  // Perform the actual projection of weights: application of weight matrix onto the source
221  // solution vector
222 
223  if( transpose )
224  {
225  // Permute the source data first
226  for( unsigned i = 0; i < srcVals.size(); ++i )
227  {
228  if( row_dtoc_dofmap[i] >= 0 )
229  m_rowVector( row_dtoc_dofmap[i] ) = srcVals[i]; // permute and set the row (source) vector properly
230  }
231 
232  // Now apply the adjoint operator: m_colVector = m_weightMatrix.adjoint() * m_rowVector;
233  deterministicSparseMatTransposeVecMulClean( m_weightMatrix, m_rowVector, m_colVector );
234  // deterministicSparseMatTransposeVecMul( m_weightMatrix, m_rowVector, m_colVector );
235  // deterministicSparseMatTransposeVecMulNative( m_weightMatrix, m_rowVector, m_colVector );
236 
237  // Permute the resulting target data back
238  for( unsigned i = 0; i < tgtVals.size(); ++i )
239  {
240  if( col_dtoc_dofmap[i] >= 0 )
241  tgtVals[i] = m_colVector( col_dtoc_dofmap[i] ); // permute and set the row (source) vector properly
242  }
243  }
244  else
245  {
246 #ifdef VERBOSE
247  output_file << "ColVector: " << m_colVector.size() << ", SrcVals: " << srcVals.size()
248  << ", Sizes: " << m_nTotDofs_SrcCov << ", " << col_dtoc_dofmap.size() << "\n";
249 #endif
250  for( unsigned i = 0; i < srcVals.size(); ++i )
251  {
252  if( col_dtoc_dofmap[i] >= 0 )
253  m_colVector( col_dtoc_dofmap[i] ) = srcVals[i]; // permute and set the row (source) vector properly
254 #ifdef VERBOSE
255  output_file << i << " " << col_gdofmap[col_dtoc_dofmap[i]] + 1 << " " << srcVals[i] << "\n";
256 #endif
257  }
258 
259  // Now apply the operator: m_rowVector = m_weightMatrix * m_colVector;
260  deterministicSparseMatVecMulClean( m_weightMatrix, m_colVector, m_rowVector );
261  // deterministicSparseMatVecMul( m_weightMatrix, m_colVector, m_rowVector );
262  // deterministicSparseMatVecMulNative( m_weightMatrix, m_colVector, m_rowVector );
263  // deterministicSparseMatVecMulKahan( m_weightMatrix, m_colVector, m_rowVector );
264 
265  // Permute the resulting target data back
266 #ifdef VERBOSE
267  output_file << "RowVector: " << m_rowVector.size() << ", TgtVals:" << tgtVals.size()
268  << ", Sizes: " << m_nTotDofs_Dest << ", " << row_gdofmap.size() << "\n";
269 #endif
270  for( unsigned i = 0; i < tgtVals.size(); ++i )
271  {
272  if( row_dtoc_dofmap[i] >= 0 )
273  {
274  tgtVals[i] = m_rowVector( row_dtoc_dofmap[i] ); // permute and set the row (source) vector properly
275 #ifdef VERBOSE
276  output_file << i << " " << row_gdofmap[row_dtoc_dofmap[i]] + 1 << " " << tgtVals[i] << "\n";
277 #endif
278  }
279  }
280  }
281 
282  // if( caasType != CAAS_NONE )
283  // {
284  // constexpr int nmax_caas_iterations = 5;
285  // double mismatch = 1.0;
286  // int caasIteration = 0;
287  // while( mismatch > 1e-15 &&
288  // caasIteration++ < nmax_caas_iterations ) // iterate until convergence or a maximum of 5 iterations
289  // {
290  // std::pair< double, double > mDefect = this->ApplyCAASLimiting( srcVals, tgtVals, caasType );
291  // if( m_remapper->verbose )
292  // printf( "Rank %d: -- Iteration: %d, Net original mass defect: %3.4e, mass defect post-CAAS: %3.4e\n",
293  // m_remapper->rank, caasIteration, mDefect.first, mDefect.second );
294  // mismatch = mDefect.second;
295  // }
296  // }
297 
298 #ifdef VERBOSE
299  output_file.flush(); // required here
300  output_file.close();
301 #endif
302 
303  // All done with matvec application
304  return moab::MB_SUCCESS;
305 }
306 
307