complex_harmonic.cc
Go to the documentation of this file.
1//LIC// ====================================================================
2//LIC// This file forms part of oomph-lib, the object-oriented,
3//LIC// multi-physics finite-element library, available
4//LIC// at http://www.oomph-lib.org.
5//LIC//
6//LIC// Copyright (C) 2006-2025 Matthias Heil and Andrew Hazel
7//LIC//
8//LIC// This library is free software; you can redistribute it and/or
9//LIC// modify it under the terms of the GNU Lesser General Public
10//LIC// License as published by the Free Software Foundation; either
11//LIC// version 2.1 of the License, or (at your option) any later version.
12//LIC//
13//LIC// This library is distributed in the hope that it will be useful,
14//LIC// but WITHOUT ANY WARRANTY; without even the implied warranty of
15//LIC// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16//LIC// Lesser General Public License for more details.
17//LIC//
18//LIC// You should have received a copy of the GNU Lesser General Public
19//LIC// License along with this library; if not, write to the Free Software
20//LIC// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21//LIC// 02110-1301 USA.
22//LIC//
23//LIC// The authors may be contacted at oomph-lib@maths.man.ac.uk.
24//LIC//
25//LIC//====================================================================
26//Driver to solve the harmonic equation as a quadratic eigenvalue problem,
27//by solving two first-order systems with homogeneous Dirichlet boundary
28//conditions. The sign is chosen to that all the eigenvalues are imaginary
29//when Mu is small (Mu=0 actually leads to a singular mass matrix so should
30//be avoided), but pairs merge to become real as Mu increases.
31
32// Generic oomph-lib routines
33#include "generic.h"
34
35// Include the mesh
36#include "meshes/one_d_mesh.h"
37
38using namespace std;
39
40using namespace oomph;
41
42/// Namespace for the shift applied to the eigenproblem
44{
45 //Parameter chosen so that only one complex-conjugate pair has merged
46 double Mu = 6.5; //Will lead to singular mass matrix if set to zero
47}
48
49
50//===================================================================
51/// Function-type-object to perform comparison of complex data types
52/// Needed to sort the complex eigenvalues into order based on the
53/// size of the real part.
54//==================================================================
55template <class T>
57{
58public:
59
60 /// Comparison in terms of magnitude of complex number
61 bool operator()(const complex<T> &x, const complex<T> &y) const
62 {
63 //Return the order in terms of magnitude if they are not equal
64 //Include a tolerance to avoid processor specific ordering
65 if(std::abs(std::abs(x) - std::abs(y)) > 1.0e-10 )
66 {return std::abs(x) < std::abs(y);}
67 //Otherwise sort on real part, again with a tolerance
68 else if(std::abs(x.real() - y.real()) > 1.0e-10)
69 {return x.real() < y.real();}
70 //Otherwise sort on imaginary part
71 else
72 {return x.imag() < y.imag();}
73 }
74};
75
76
77//=================================================================
78/// A class for all elements that solve the eigenvalue problem
79/// \f[ \frac{\partial w}{\partial x} = \lambda u \f]
80/// \f[ \frac{\partial u}{\partial x} = (\lambda - \mu) w \f]
81/// This class contains the generic maths. Shape functions, geometric
82/// mapping etc. must get implemented in derived class.
83//================================================================
84class ComplexHarmonicEquations : public virtual FiniteElement
85{
86
87public:
88 /// Empty Constructor
90
91 /// Access function: First eigenfunction value at local node n
92 /// Note that solving the eigenproblem does not assign values
93 /// to this storage space. It is used for output purposes only.
94 virtual inline double u(const unsigned& n) const
95 {return nodal_value(n,0);}
96
97 /// Second eigenfunction value at local node n
98 virtual inline double w(const unsigned& n) const
99 {return nodal_value(n,1);}
100
101 /// Output the eigenfunction with default number of plot points
102 void output(ostream &outfile)
103 {
104 unsigned nplot=5;
105 output(outfile,nplot);
106 }
107
108 /// Output FE representation of soln: x,y,u or x,y,z,u at
109 /// Nplot plot points
110 void output(ostream &outfile, const unsigned &nplot)
111 {
112 //Vector of local coordinates
113 Vector<double> s(1);
114
115 // Tecplot header info
116 outfile << tecplot_zone_string(nplot);
117
118 // Loop over plot points
119 unsigned num_plot_points=nplot_points(nplot);
120 for (unsigned iplot=0;iplot<num_plot_points;iplot++)
121 {
122 // Get local coordinates of plot point
123 get_s_plot(iplot,nplot,s);
124 //Output the coordinate and the eigenfunction
125 outfile << interpolated_x(s,0) << " " << interpolated_u(s)
126 << " " << interpolated_w(s) << std::endl;
127 }
128 // Write tecplot footer (e.g. FE connectivity lists)
129 write_tecplot_zone_footer(outfile,nplot);
130 }
131
132 /// Assemble the contributions to the jacobian and mass
133 /// matrices
135 Vector<double> &residuals,
136 DenseMatrix<double> &jacobian, DenseMatrix<double> &mass_matrix)
137 {
138 //Find out how many nodes there are
139 unsigned n_node = nnode();
140
141 //Set up memory for the shape functions and their derivatives
142 Shape psi(n_node);
143 DShape dpsidx(n_node,1);
144
145 //Set the number of integration points
146 unsigned n_intpt = integral_pt()->nweight();
147
148 //Integers to store the local equation and unknown numbers
149 int local_eqn=0, local_unknown=0;
150
151 //Loop over the integration points
152 for(unsigned ipt=0;ipt<n_intpt;ipt++)
153 {
154 //Get the integral weight
155 double w = integral_pt()->weight(ipt);
156
157 //Call the derivatives of the shape and test functions
158 double J = dshape_eulerian_at_knot(ipt,psi,dpsidx);
159
160 //Premultiply the weights and the Jacobian
161 double W = w*J;
162
163 //Assemble the contributions to the mass matrix
164 //Loop over the test functions
165 for(unsigned l=0;l<n_node;l++)
166 {
167 //Get the local equation number
168 local_eqn = u_local_eqn(l,0);
169 //If it's not a boundary condition
170 if(local_eqn >= 0)
171 {
172 //Loop over the shape functions
173 for(unsigned l2=0;l2<n_node;l2++)
174 {
175 local_unknown = u_local_eqn(l2,0);
176 //If at a non-zero degree of freedom add in the entry
177 if(local_unknown >= 0)
178 {
179 //This corresponds to the top left B block
180 mass_matrix(local_eqn, local_unknown) += psi(l2)*psi(l)*W;
181 }
182 local_unknown = u_local_eqn(l2,1);
183 //If at a non-zero degree of freedom add in the entry
184 if(local_unknown >= 0)
185 {
186 //This corresponds to the top right A block
187 jacobian(local_eqn,local_unknown) += dpsidx(l2,0)*psi(l)*W;
188 }
189 }
190 }
191
192 //Get the local equation number
193 local_eqn = u_local_eqn(l,1);
194 //IF it's not a boundary condition
195 if(local_eqn >= 0)
196 {
197 //Loop over the shape functions
198 for(unsigned l2=0;l2<n_node;l2++)
199 {
200 local_unknown = u_local_eqn(l2,0);
201 //If at a non-zero degree of freedom add in the entry
202 if(local_unknown >= 0)
203 {
204 //This corresponds to the lower left A block
205 jacobian(local_eqn,local_unknown) += dpsidx(l2,0)*psi(l)*W;
206 }
207 local_unknown = u_local_eqn(l2,1);
208 //If at a non-zero degree of freedom add in the entry
209 if(local_unknown >= 0)
210 {
211 //This corresponds to the lower right B block
212 mass_matrix(local_eqn, local_unknown) += psi(l2)*psi(l)*W;
213 //This corresponds to the lower right \mu B block
214 jacobian(local_eqn,local_unknown) +=
215 EigenproblemShift::Mu*psi(l2)*psi(l)*W;
216 }
217 }
218 }
219 }
220 }
221 } //end_of_fill_in_contribution_to_jacobian_and_mass_matrix
222
223 /// Return FE representation of function value u(s) at local coordinate s
224 inline double interpolated_u(const Vector<double> &s) const
225 {
226 unsigned n_node = nnode();
227
228 //Local shape functions
229 Shape psi(n_node);
230
231 //Find values of basis function
232 this->shape(s,psi);
233
234 //Initialise value of u
235 double interpolated_u = 0.0;
236
237 //Loop over the local nodes and sum
238 for(unsigned l=0;l<n_node;l++) {interpolated_u+=u(l)*psi[l];}
239
240 //Return the interpolated value of the eigenfunction
241 return(interpolated_u);
242 }
243
244 /// Return FE representation of function value w(s) at local coordinate s
245 inline double interpolated_w(const Vector<double> &s) const
246 {
247 unsigned n_node = nnode();
248
249 //Local shape functions
250 Shape psi(n_node);
251
252 //Find values of basis function
253 this->shape(s,psi);
254
255 //Initialise value of u
256 double interpolated_u = 0.0;
257
258 //Loop over the local nodes and sum
259 for(unsigned l=0;l<n_node;l++) {interpolated_u+=w(l)*psi[l];}
260
261 //Return the interpolated value of the eigenfunction
262 return(interpolated_u);
263 }
264
265
266protected:
267
268 /// Shape/test functions and derivs w.r.t. to global coords at
269 /// local coord. s; return Jacobian of mapping
270 virtual double dshape_eulerian(const Vector<double> &s,
271 Shape &psi,
272 DShape &dpsidx) const=0;
273
274 /// Shape/test functions and derivs w.r.t. to global coords at
275 /// integration point ipt; return Jacobian of mapping
276 virtual double dshape_eulerian_at_knot(const unsigned &ipt,
277 Shape &psi,
278 DShape &dpsidx) const=0;
279
280 /// Access function that returns the local equation number
281 /// of the unknown in the problem. Default is to assume that it is the
282 /// first (only) value stored at the node.
283 virtual inline int u_local_eqn(const unsigned &n, const unsigned &i)
284 {return nodal_local_eqn(n,i);}
285
286 private:
287
288};
289
290
291
292//======================================================================
293/// QComplexHarmonicElement<NNODE_1D> elements are 1D Elements with
294/// NNODE_1D nodal points that are used to solve the ComplexHarmonic eigenvalue
295/// Problem described by ComplexHarmonicEquations.
296//======================================================================
297template <unsigned NNODE_1D>
298class QComplexHarmonicElement : public virtual QElement<1,NNODE_1D>,
300{
301
302 public:
303
304 /// Constructor: Call constructors for QElement and
305 /// Poisson equations
306 QComplexHarmonicElement() : QElement<1,NNODE_1D>(),
308
309 /// Required # of `values' (pinned or dofs)
310 /// at node n. Here there are two (u and w)
311 inline unsigned required_nvalue(const unsigned &n) const {return 2;}
312
313 /// Output function overloaded from ComplexHarmonicEquations
314 void output(ostream &outfile)
316
317 /// Output function overloaded from ComplexHarmonicEquations
318 void output(ostream &outfile, const unsigned &Nplot)
319 {ComplexHarmonicEquations::output(outfile,Nplot);}
320
321
322protected:
323
324/// Shape, test functions & derivs. w.r.t. to global coords. Return Jacobian.
325 inline double dshape_eulerian(const Vector<double> &s,
326 Shape &psi,
327 DShape &dpsidx) const
328 {return QElement<1,NNODE_1D>::dshape_eulerian(s,psi,dpsidx);}
329
330
331 /// Shape, test functions & derivs. w.r.t. to global coords. at
332 /// integration point ipt. Return Jacobian.
333 inline double dshape_eulerian_at_knot(const unsigned& ipt,
334 Shape &psi,
335 DShape &dpsidx) const
336 {return QElement<1,NNODE_1D>::dshape_eulerian_at_knot(ipt,psi,dpsidx);}
337
338}; //end_of_QComplexHarmonic_class_definition
339
340
341//==start_of_problem_class============================================
342/// 1D ComplexHarmonic problem in unit interval.
343//====================================================================
344template<class ELEMENT,class EIGEN_SOLVER>
345class ComplexHarmonicProblem : public Problem
346{
347public:
348
349 /// Constructor: Pass number of elements and pointer to source function
350 ComplexHarmonicProblem(const unsigned& n_element);
351
352 /// Destructor. Clean up the mesh and solver
353 ~ComplexHarmonicProblem(){delete this->mesh_pt();
354 delete this->eigen_solver_pt();}
355
356 /// Solve the problem
357 void solve(const unsigned &label);
358
359 /// Doc the solution, pass the number of the case considered,
360 /// so that output files can be distinguished.
361 void doc_solution(const unsigned& label);
362
363}; // end of problem class
364
365
366
367//=====start_of_constructor===============================================
368/// Constructor for 1D ComplexHarmonic problem in unit interval.
369/// Discretise the 1D domain with n_element elements of type ELEMENT.
370/// Specify function pointer to source function.
371//========================================================================
372template<class ELEMENT,class EIGEN_SOLVER>
374 const unsigned& n_element)
375{
376 //Create the eigen solver
377 this->eigen_solver_pt() = new EIGEN_SOLVER;
378
379 //Set domain length
380 double L=1.0;
381
382 // Build mesh and store pointer in Problem
383 Problem::mesh_pt() = new OneDMesh<ELEMENT>(n_element,L);
384
385 // Set the boundary conditions for this problem: By default, all nodal
386 // values are free -- we only need to pin the ones that have
387 // Dirichlet conditions.
388
389 // Pin the single nodal value at the single node on mesh
390 // boundary 0 (= the left domain boundary at x=0)
391 mesh_pt()->boundary_node_pt(0,0)->pin(0);
392
393 // Pin the single nodal value at the single node on mesh
394 // boundary 1 (= the right domain boundary at x=1)
395 mesh_pt()->boundary_node_pt(1,0)->pin(0);
396
397 // Setup equation numbering scheme
398 assign_eqn_numbers();
399
400} // end of constructor
401
402
403
404//===start_of_doc=========================================================
405/// Doc the solution in tecplot format. Label files with label.
406//========================================================================
407template<class ELEMENT,class EIGEN_SOLVER>
409 const unsigned& label)
410{
411
412 ofstream some_file;
413 char filename[100];
414
415 // Number of plot points
416 unsigned npts;
417 npts=5;
418
419 // Output solution with specified number of plot points per element
420 snprintf(filename, sizeof(filename), "soln%i.dat",label);
421 some_file.open(filename);
422 mesh_pt()->output(some_file,npts);
423 some_file.close();
424
425} // end of doc
426
427//=======================start_of_solve==============================
428/// Solve the eigenproblem
429//===================================================================
430template<class ELEMENT,class EIGEN_SOLVER>
432solve(const unsigned& label)
433{
434 //Set external storage for the eigenvalues
435 Vector<complex<double> > eigenvalues;
436 //Set external storage for the eigenvectors
437 Vector<DoubleVector> eigenvector_real;
438 Vector<DoubleVector> eigenvector_imag;
439 //Desired number eigenvalues enough to include the first two real
440 //eigenvalues and then the complex conjugate pair. If the number is set
441 //to four then the original iterative solver prefers the degenerate (real) eigenvalues Mu, Mu.
442 unsigned n_eval=7;
443
444 //Solve the eigenproblem
445 this->solve_eigenproblem(n_eval,eigenvalues,eigenvector_real,eigenvector_imag);
446
447 //We now need to sort the output based on the size of the real part
448 //of the eigenvalues.
449 //This is because the solver does not necessarily sort the eigenvalues
450 Vector<complex<double> > sorted_eigenvalues = eigenvalues;
451 sort(sorted_eigenvalues.begin(),sorted_eigenvalues.end(),
453
454 //Read out the smallest eigenvalue
455 complex<double> temp_evalue = sorted_eigenvalues[0];
456 unsigned smallest_index=0;
457 //Loop over the unsorted eigenvalues and find the entry that corresponds
458 //to our second smallest eigenvalue.
459 for(unsigned i=0;i<eigenvalues.size();i++)
460 {
461 //Note that equality tests for doubles are bad, but it was just
462 //sorted data, so should be fine
463 if(eigenvalues[i] == temp_evalue) {smallest_index=i; break;}
464 }
465
466 //Normalise the eigenvector
467 {
468 //Get the dimension of the eigenvector
469 unsigned dim = eigenvector_real[smallest_index].nrow();
470 double length=0.0;
471 //Loop over all the entries
472 for(unsigned i=0;i<dim;i++)
473 {
474 //Add the contribution to the length
475 length += std::pow(eigenvector_real[smallest_index][i],2.0);
476 }
477 //Now take the magnitude
478 length = sqrt(length);
479 //Fix the sign
480 if(eigenvector_real[smallest_index][0] < 0) {length *= -1.0;}
481 //Finally normalise
482 for(unsigned i=0;i<dim;i++)
483 {
484 eigenvector_real[smallest_index][i] /= length;
485 }
486 }
487
488 //Now assign the second eigenvector to the dofs of the problem
489 this->assign_eigenvector_to_dofs(eigenvector_real[smallest_index]);
490 //Output solution for this case (label output files with "1")
491 this->doc_solution(label);
492
493 char filename[100];
494 snprintf(filename, sizeof(filename), "eigenvalues%i.dat",label);
495
496 //Open an output file for the sorted eigenvalues
497 ofstream evalues(filename);
498 for(unsigned i=0;i<n_eval;i++)
499 {
500 //Print to screen
501 cout << sorted_eigenvalues[i].real() << " "
502 << sorted_eigenvalues[i].imag() << std::endl;
503 //Send to file
504 evalues << sorted_eigenvalues[i].real() << " "
505 << sorted_eigenvalues[i].imag() << std::endl;
506 }
507
508 evalues.close();
509} //end_of_solve
510
511
512////////////////////////////////////////////////////////////////////////
513////////////////////////////////////////////////////////////////////////
514////////////////////////////////////////////////////////////////////////
515
516
517//======start_of_main==================================================
518/// Driver for 1D Poisson problem
519//=====================================================================
520int main(int argc, char **argv)
521{
522//Want to test Trilinos if we have it, so we must initialise MPI
523//if we have compiled with it
524#ifdef OOMPH_HAS_MPI
525 MPI_Helpers::init(argc,argv);
526#endif
527
528 // Set up the problem:
529 unsigned n_element=100; //Number of elements
530
531 clock_t t_start1 = clock();
532 //Solve with LAPACK_QZ
533 {
534
536 problem(n_element);
537
538 problem.solve(1);
539 }
540 clock_t t_end1 = clock();
541
542#ifdef OOMPH_HAS_TRILINOS
543 clock_t t_start2 = clock();
544//Solve with Anasazi
545 {
546 ComplexHarmonicProblem<QComplexHarmonicElement<3>,ANASAZI> problem(n_element);
547 problem.solve(2);
548 }
549 clock_t t_end2 = clock();
550#endif
551
552 std::cout << "LAPACK TIME: " << (double)(t_end1 - t_start1)/CLOCKS_PER_SEC
553 << std::endl;
554
555#ifdef OOMPH_HAS_TRILINOS
556 std::cout << "ANASAZI TIME: " << (double)(t_end2 - t_start2)/CLOCKS_PER_SEC
557 << std::endl;
558#endif
559
560#ifdef OOMPH_HAS_MPI
561 MPI_Helpers::finalize();
562#endif
563
564} // end of main
565
566
567
568
569
570
571
572
573
574
A class for all elements that solve the eigenvalue problem.
void output(ostream &outfile, const unsigned &nplot)
Output FE representation of soln: x,y,u or x,y,z,u at Nplot plot points.
virtual int u_local_eqn(const unsigned &n, const unsigned &i)
Access function that returns the local equation number of the unknown in the problem....
void fill_in_contribution_to_jacobian_and_mass_matrix(Vector< double > &residuals, DenseMatrix< double > &jacobian, DenseMatrix< double > &mass_matrix)
Assemble the contributions to the jacobian and mass matrices.
virtual double dshape_eulerian(const Vector< double > &s, Shape &psi, DShape &dpsidx) const =0
Shape/test functions and derivs w.r.t. to global coords at local coord. s; return Jacobian of mapping...
virtual double dshape_eulerian_at_knot(const unsigned &ipt, Shape &psi, DShape &dpsidx) const =0
Shape/test functions and derivs w.r.t. to global coords at integration point ipt; return Jacobian of ...
double interpolated_w(const Vector< double > &s) const
Return FE representation of function value w(s) at local coordinate s.
void output(ostream &outfile)
Output the eigenfunction with default number of plot points.
virtual double w(const unsigned &n) const
Second eigenfunction value at local node n.
double interpolated_u(const Vector< double > &s) const
Return FE representation of function value u(s) at local coordinate s.
ComplexHarmonicEquations()
Empty Constructor.
virtual double u(const unsigned &n) const
Access function: First eigenfunction value at local node n Note that solving the eigenproblem does no...
1D ComplexHarmonic problem in unit interval.
~ComplexHarmonicProblem()
Destructor. Clean up the mesh and solver.
void solve(const unsigned &label)
Solve the problem.
void doc_solution(const unsigned &label)
Doc the solution, pass the number of the case considered, so that output files can be distinguished.
ComplexHarmonicProblem(const unsigned &n_element)
Constructor: Pass number of elements and pointer to source function.
Function-type-object to perform comparison of complex data types Needed to sort the complex eigenvalu...
bool operator()(const complex< T > &x, const complex< T > &y) const
Comparison in terms of magnitude of complex number.
QComplexHarmonicElement<NNODE_1D> elements are 1D Elements with NNODE_1D nodal points that are used t...
unsigned required_nvalue(const unsigned &n) const
Required # of ‘values’ (pinned or dofs) at node n. Here there are two (u and w)
double dshape_eulerian_at_knot(const unsigned &ipt, Shape &psi, DShape &dpsidx) const
Shape, test functions & derivs. w.r.t. to global coords. at integration point ipt....
double dshape_eulerian(const Vector< double > &s, Shape &psi, DShape &dpsidx) const
Shape, test functions & derivs. w.r.t. to global coords. Return Jacobian.
void output(ostream &outfile, const unsigned &Nplot)
Output function overloaded from ComplexHarmonicEquations.
void output(ostream &outfile)
Output function overloaded from ComplexHarmonicEquations.
QComplexHarmonicElement()
Constructor: Call constructors for QElement and Poisson equations.
int main(int argc, char **argv)
Driver for 1D Poisson problem.
Namespace for the shift applied to the eigenproblem.