LehrFEM++ 1.0.0
A simple Finite Element Library for teaching
whitney_one_basis_expansion_coeffs.h
1#ifndef HLDO_SPHERE_DEBUGGING_WHITNEY_ONE_BASIS_EXPANSION_COEFFS_H
2#define HLDO_SPHERE_DEBUGGING_WHITNEY_ONE_BASIS_EXPANSION_COEFFS_H
3
8#include <lf/assemble/coomatrix.h>
9#include <lf/mesh/hybrid2d/mesh_factory.h>
10#include <lf/mesh/mesh.h>
11#include <lf/mesh/utils/tp_triag_mesh_builder.h>
12#include <lf/mesh/utils/utils.h>
13#include <lf/quad/quad.h>
14#include <lf/refinement/refinement.h>
15#include <lf/uscalfe/uscalfe.h>
16#include <norms.h>
17#include <results_processing.h>
18#include <sphere_triag_mesh_builder.h>
19
20#include <Eigen/Dense>
21#include <cmath>
22#include <iomanip>
23#include <iostream>
24
25#include "debugging.h"
26
27namespace projects::hldo_sphere {
28
29namespace debugging {
30
31using lf::uscalfe::operator-;
32
46 public:
54 auto u = [](Eigen::Matrix<double, 3, 1> x) -> Eigen::Matrix<double, 3, 1> {
55 return Eigen::Matrix<double, 3, 1>::Zero();
56 };
57 u_ = u;
58 mu_ = Eigen::VectorXd::Zero(1);
59 res_ = Eigen::VectorXd::Zero(1);
60
61 // create mesh factory for basic mesh only because empty values do not
62 // complile
63 std::unique_ptr<lf::mesh::MeshFactory> factory =
64 std::make_unique<lf::mesh::hybrid2d::MeshFactory>(3);
65 std::shared_ptr<projects::hldo_sphere::mesh::SphereTriagMeshBuilder>
66 sphere = std::make_shared<
68 std::move(factory));
69 sphere->setRefinementLevel(0);
70 sphere->setRadius(1);
71 mesh_ = sphere->Build();
72 }
73
82 void Compute() {
83 const lf::assemble::size_type n_edge(mesh_->NumEntities(1));
84 lf::assemble::COOMatrix<double> coo_mat(n_edge * 3, n_edge);
85
86 // Compute the Constraint Matrix
87 // By iterating over all the triangles and adding the necessary entries
88 //
89 // That is for each triangle compute the local basis functions and add their
90 // contributions at all the edge midpoints to the matrix
91 for (const lf::mesh::Entity *cell_p : mesh_->Entities(0)) {
92 // Only triangles are supported
93 LF_VERIFY_MSG(cell_p->RefEl() == lf::base::RefEl::kTria(),
94 "Unsupported cell type " << cell_p->RefEl());
95
96 // Get the geometry of the entity
97 const auto *geom = cell_p->Geometry();
98
99 // Compute the global vertex coordinates
100 Eigen::MatrixXd vertices = geom->Global(cell_p->RefEl().NodeCoords());
101
102 // Construct the basis functions from curl of the standard hat functions
104 // The gradients are constant on the triangle
105 const Eigen::MatrixXd ref_grads =
106 hat_func.GradientsReferenceShapeFunctions(Eigen::VectorXd::Zero(2))
107 .transpose();
108 // The JacobianInverseGramian is constant on the triangle
109 const Eigen::MatrixXd J_inv_trans =
110 geom->JacobianInverseGramian(Eigen::VectorXd::Zero(2));
111
112 // get the gradients
113 const Eigen::MatrixXd grad = J_inv_trans * ref_grads;
114
115 // get edge orientations
116 auto edgeOrientations = cell_p->RelativeOrientations();
117 Eigen::Vector3d s;
118 s << lf::mesh::to_sign(edgeOrientations[0]),
119 lf::mesh::to_sign(edgeOrientations[1]),
120 lf::mesh::to_sign(edgeOrientations[2]);
121
122 auto edges = cell_p->SubEntities(1);
123
124 Eigen::Matrix<lf::base::size_type, 3, 1> global_idx;
125 global_idx << mesh_->Index(*edges[0]), mesh_->Index(*edges[1]),
126 mesh_->Index(*edges[2]);
127
128 // loop over edges in the cell
129 for (int i = 0; i < 3; i++) {
130 int i_p1 = (i + 1) % 3;
131 int i_p2 = (i + 2) % 3;
132
133 // compute entries (note we use weight 1/2
134 Eigen::Vector3d ent_i_p1 = s(i_p1) / 4. * grad.col(i_p2);
135 Eigen::Vector3d ent_i_p2 = -s(i_p2) / 4. * grad.col(i_p2);
136 Eigen::Vector3d ent_i = s(i) / 4. * (grad.col(i_p1) - grad.col(i));
137
138 // Add Matrix entries
139 coo_mat.AddToEntry(3 * global_idx[i], global_idx[i_p1], ent_i_p1(0));
140 coo_mat.AddToEntry(3 * global_idx[i] + 1, global_idx[i_p1],
141 ent_i_p1(1));
142 coo_mat.AddToEntry(3 * global_idx[i] + 2, global_idx[i_p1],
143 ent_i_p1(2));
144
145 coo_mat.AddToEntry(3 * global_idx[i], global_idx[i_p2], ent_i_p2(0));
146 coo_mat.AddToEntry(3 * global_idx[i] + 1, global_idx[i_p2],
147 ent_i_p2(1));
148 coo_mat.AddToEntry(3 * global_idx[i] + 2, global_idx[i_p2],
149 ent_i_p2(2));
150
151 coo_mat.AddToEntry(3 * global_idx[i], global_idx[i], ent_i(0));
152 coo_mat.AddToEntry(3 * global_idx[i] + 1, global_idx[i], ent_i(1));
153 coo_mat.AddToEntry(3 * global_idx[i] + 2, global_idx[i], ent_i(2));
154 } // loop over local edges
155 } // loop cells
156
157 // compute righthandside vector
158 Eigen::VectorXd phi(3 * n_edge);
159 for (const lf::mesh::Entity *edge_p : mesh_->Entities(1)) {
160 const auto *geom = edge_p->Geometry();
161 Eigen::MatrixXd vertices = geom->Global(edge_p->RefEl().NodeCoords());
162 Eigen::Vector3d midpoint = 1. / 2. * vertices.col(0) + vertices.col(1);
163
164 // Assign the 3 corresponding entries
165 lf::base::size_type glob_idx = mesh_->Index(*edge_p);
166 phi.segment(3 * glob_idx, 3) = u_(midpoint);
167 }
168
169 // solve the matrix system and store the result
170 Eigen::SparseQR<Eigen::SparseMatrix<double>, Eigen::COLAMDOrdering<int>>
171 solver;
172 Eigen::SparseMatrix<double> sparse_mat = coo_mat.makeSparse();
173 sparse_mat.makeCompressed();
174 solver.analyzePattern(sparse_mat);
175 solver.factorize(sparse_mat);
176 if (solver.info() != Eigen::Success) {
177 throw std::runtime_error("Could not decompose the matrix");
178 }
179
180 mu_ = solver.solve(phi);
181
182 Eigen::VectorXd phi_hat = sparse_mat * mu_;
183
184 res_ = phi_hat - phi;
185
186 } // Compute
187
192 void SetFunction(std::function<Eigen::Matrix<double, 3, 1>(
193 const Eigen::Matrix<double, 3, 1> &)>
194 u) {
195 u_ = u;
196 }
197
198 /*
199 * @ brief set the mesh for the computations
200 *
201 * @param mesh pointer to the mesh
202 *
203 */
204 void SetMesh(std::shared_ptr<const lf::mesh::Mesh> mesh) { mesh_ = mesh; }
205
209 Eigen::VectorXd GetMu() { return mu_; }
210
215 double GetL2Error() {
216 auto square_vector =
217 [](Eigen::Matrix<double, Eigen::Dynamic, 1> a) -> double {
218 return a.squaredNorm();
219 };
220 // define quadrule for norms
224 mesh_);
226 auto mf_diff = mf_uh - mf_u;
227 const std::pair<double, lf::mesh::utils::CodimMeshDataSet<double>> l2 =
229 square_vector, qr);
230 double error = std::get<0>(l2);
231 return error;
232 }
233
238 double GetSquaredResidualError() { return res_.squaredNorm(); }
239
240 /*
241 * @brief conducts convergence experiment with the given number of
242 refinement_level and
243 * the function u
244 *
245 * @param refinement_level the maximal refinement_level considered
246 * @param u function to approximate
247 *
248 */
249 static void Experiemnt(
250 unsigned refinement_level,
251 std::function<Eigen::Vector3d(const Eigen::Vector3d &)> u) {
252 std::vector<std::shared_ptr<const lf::mesh::Mesh>> meshs =
253 std::vector<std::shared_ptr<const lf::mesh::Mesh>>(refinement_level +
254 1);
255
256 // create vecotor with the l2 error in the first component and the
257 // squaredresiduals in the second
258 std::vector<std::pair<double, double>> solutions(refinement_level + 1);
259
260 // Read the mesh
261 std::unique_ptr<lf::mesh::MeshFactory> factory =
262 std::make_unique<lf::mesh::hybrid2d::MeshFactory>(3);
263
266
267 // we always take the same radius
268 sphere.setRadius(1.);
269
270 // create a coefficients creator object
271 WhitneyOneBasisExpansionCoeffs coeff_creator;
272 coeff_creator.SetFunction(u);
273
274 // start timer for total time
275 std::chrono::steady_clock::time_point start_time_total =
276 std::chrono::steady_clock::now();
277
278 for (unsigned il = 0; il <= refinement_level; ++il) {
279 std::cout << "\nStart computation of refinement_level " << il
280 << std::flush;
281
282 // start timer
283 std::chrono::steady_clock::time_point start_time =
284 std::chrono::steady_clock::now();
285
286 // get mesh
287 sphere.setRefinementLevel(il);
288 const std::shared_ptr<lf::mesh::Mesh> mesh = sphere.Build();
289 meshs[il] = mesh;
290
291 coeff_creator.SetMesh(mesh);
292
293 // end timer
294 std::chrono::steady_clock::time_point end_time =
295 std::chrono::steady_clock::now();
296 double elapsed_sec =
297 std::chrono::duration_cast<std::chrono::milliseconds>(end_time -
298 start_time)
299 .count() /
300 1000.;
301
302 std::cout << " -> Built Mesh " << elapsed_sec << " [s] " << std::flush;
303
304 // start timer
305 start_time = std::chrono::steady_clock::now();
306
307 coeff_creator.Compute();
308
309 // end timer
310 end_time = std::chrono::steady_clock::now();
311 elapsed_sec = std::chrono::duration_cast<std::chrono::milliseconds>(
312 end_time - start_time)
313 .count() /
314 1000.;
315
316 std::cout << " -> Computed basis expansion coefficients " << elapsed_sec
317 << " [s] " << std::flush;
318
319 // start timer
320 start_time = std::chrono::steady_clock::now();
321
322 solutions[il].first = coeff_creator.GetL2Error();
323 solutions[il].second = coeff_creator.GetSquaredResidualError();
324
325 // end timer
326 end_time = std::chrono::steady_clock::now();
327
328 elapsed_sec = std::chrono::duration_cast<std::chrono::milliseconds>(
329 end_time - start_time)
330 .count() /
331 1000.;
332
333 std::cout << " -> Computed Errors " << elapsed_sec << " [s] "
334 << std::flush;
335
336 } // end loop level
337
338 // end timer total
339 std::chrono::steady_clock::time_point end_time_total =
340 std::chrono::steady_clock::now();
341 double elapsed_sec_total =
342 std::chrono::duration_cast<std::chrono::milliseconds>(end_time_total -
343 start_time_total)
344 .count() /
345 1000.;
346
347 std::cout << "\nTotal computation time for all levels " << elapsed_sec_total
348 << " [s]\n";
349
350 // create results direcotry
351 std::string main_dir = "results";
352 std::filesystem::create_directory(main_dir);
353
354 // prepare output
355 int table_width = 13;
356 int precision = 4;
357
358 // create csv file
359 std::ofstream csv_file;
360 std::string csv_name = concat("basis_expansion_coeffs_", refinement_level);
361 std::replace(csv_name.begin(), csv_name.end(), '.', '_');
362 csv_file.open(concat(main_dir, "/", csv_name, ".csv"));
363 csv_file << "numCells,"
364 << "numEdges,"
365 << "numVerts,"
366 << "hMax";
367
368 // introduce columns for errors
369 csv_file << ",L2Error,SquaredRes";
370 csv_file << std::endl;
371
372 Eigen::VectorXd hMax(refinement_level + 1);
373 hMax.setZero();
374
375 // loop over all levels contained in the solution
376 for (lf::base::size_type lvl = 0; lvl <= refinement_level; ++lvl) {
377 // create mesh Functions for solutions the level
378 auto &sol_mesh = meshs[lvl];
379
380 // compute meshwidth
381 for (const lf::mesh::Entity *e : sol_mesh->Entities(1)) {
382 double h = lf::geometry::Volume(*(e->Geometry()));
383 if (h > hMax(lvl)) hMax(lvl) = h;
384 }
385
386 // print level and mesh informations
387 csv_file << sol_mesh->NumEntities(0) << "," << sol_mesh->NumEntities(1)
388 << "," << sol_mesh->NumEntities(2) << "," << hMax(lvl);
389
390 csv_file << "," << solutions[lvl].first << "," << solutions[lvl].second
391 << "\n";
392 } // end loop over levels
393
394 // close csv file
395 csv_file.close();
396 }
397
398 private:
399 std::function<Eigen::Matrix<double, 3, 1>(
400 const Eigen::Matrix<double, 3, 1> &)>
402 Eigen::VectorXd mu_;
403 Eigen::VectorXd res_;
404 std::shared_ptr<const lf::mesh::Mesh> mesh_;
405};
406
407} // namespace debugging
408
409} // namespace projects::hldo_sphere
410
411#endif // THESIS_DEBUGGING_WHITNEY_ONE_BASIS_EXPANSION_COEFFS_H
A temporary data structure for matrix in COO format.
Definition: coomatrix.h:52
Eigen::SparseMatrix< Scalar > makeSparse() const
Create an Eigen::SparseMatrix out of the COO format.
Definition: coomatrix.h:172
void AddToEntry(gdof_idx_t i, gdof_idx_t j, SCALAR increment)
Add a value to the specified entry.
Definition: coomatrix.h:87
constexpr RefEl(RefElType type) noexcept
Create a RefEl from a lf::base::RefElType enum.
Definition: ref_el.h:172
static constexpr RefEl kTria()
Returns the reference triangle.
Definition: ref_el.h:158
Interface class representing a topological entity in a cellular complex
Definition: entity.h:39
MeshFunction wrapper for a simple function of physical coordinates.
Represents a Quadrature Rule over one of the Reference Elements.
Definition: quad_rule.h:58
Linear Lagrange finite element on triangular reference element.
Definition: lagr_fe.h:58
Eigen::Matrix< SCALAR, Eigen::Dynamic, Eigen::Dynamic > GradientsReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override
Computation of the gradients of all reference shape functions in a number of points.
Definition: lagr_fe.h:117
Class to find the "best" basisexpansion coefficients with the analytical solution.
double GetL2Error()
returns L2 Error between the approximation and the analytical solution
void SetFunction(std::function< Eigen::Matrix< double, 3, 1 >(const Eigen::Matrix< double, 3, 1 > &)> u)
Sets the analytical solution.
double GetSquaredResidualError()
returns the squared sum of all residuals at the edge midpoints, this is the quanity minimized in the ...
std::function< Eigen::Matrix< double, 3, 1 >(const Eigen::Matrix< double, 3, 1 > &)> u_
static void Experiemnt(unsigned refinement_level, std::function< Eigen::Vector3d(const Eigen::Vector3d &)> u)
Eigen::VectorXd GetMu()
returns basis expansion coefficients computed in Compute
void setRefinementLevel(lf::base::size_type n)
Set the refinement level.
std::shared_ptr< lf::mesh::Mesh > Build()
Build the mesh.
Provides Mesh Function for given basis expansion coefficients.
unsigned int size_type
general type for variables related to size of arrays
Definition: base.h:24
lf::base::size_type size_type
double Volume(const Geometry &geo)
Compute the (approximate) volume (area) of a shape.
int to_sign(Orientation o)
Definition: entity.cc:7
QuadRule make_QuadRule(base::RefEl ref_el, unsigned degree)
Returns a QuadRule object for the given Reference Element and Degree.
static std::string concat(Args &&...args)
Concatenate objects defining an operator<<(std::ostream&)
Definition: debugging.h:26
std::pair< double, lf::mesh::utils::CodimMeshDataSet< double > > L2norm(const std::shared_ptr< const lf::mesh::Mesh > &mesh_p, const MF &f, const SQ_F &sq_f, const lf::quad::QuadRule &quadrule)
Computes the L2Norm of some MeshFunction (type MF) f.
Definition: norms.h:33
Implementation of the thesis Hogde Laplacians and Dirac Operators on the surface of the 3-Sphere.
Definition: assemble.h:15