Post-processing#
Save data to external files#
We want to exploit easily the data contained in the objects produced during the computation. The objects concerned are mainly TermVector
objects since TermVector
is the type of nearly all the computation results of interest to the user. This may also concern TermMatrix
objects if further postprocessing is envisaged.
As mentionned just above, printing objects using the <<
operator may produce big files containing a lot of information, generally used to control the different steps of the computation. But we often need to handle the data values outside XLiFE++, either in a raw format or in a specific one corresponding to some particular software.
The saveToFile
external routines have been designed for that purpose.
They all work with the same pattern. First argument is the file name, with or without its extension. The second argument is the object yyou want to save. It can be a Mesh
, a TermMatrix
, a TermVector
, a TermVectors
, a EigenElements
or a SvdElements
. Then come the optional arguments with a key-value system.
The key _format
, available for all the above-mentioned objects but TermMatrix
, concerns the file format. Thhe most important thing to know about this key is that if the filename contains an authorized extension, this extension will always be prior to the value given to _format
. Authorized extensions depend on the object to be saved.
Save a Mesh
#
Authorized file extensions are:
Mesh m(...);
saveToFile("mesh.msh", m); // will produce a file mesh.msh with the gmsh format
saveToFile("mesh", m, _format=vtk); // will produce a file mesh.vtk with the vtk format
You have an additionnal parameter to control if your Mesh
save generates only one file (key _aFilePerDomain
without value, the default behavior), or a file per domain (key _aFilePerDomain
without value).
Save a TermVector
, a TermVectors
, a EigenElements
or a SvdElements
#
For these objects, authorized file extensions are:
.msh for Gmsh format (
msh
).vtk for Paraview VTK format (
vtk
).vtu for Paraview VTU format (
vtu
).m for Matlab script format (
matlab
).xyzv for
xyzv
format (Data are stored in four columns : \(x_i \quad y_i \quad z_i \quad \mathrm{val}_i\)).txt for
raw
format
Mesh m(Rectangle(_origin=Point(0.,0.), _xlength=5, _ylength=2, _hsteps=0.01, _domain_name="Omega"), _generator=structured);
...
Space V(_domain=omega, ...);
...
TermVector U(...);
saveToFile("U.msh", U); // will produce a file U_Omega.msh with the gmsh format
saveToFile("U", U, _format=vtu); // will produce a file U_Omega.vtu with the vtu format
Hint
For a TermVector
, between the file name and optional parameters, you can give up to 4 TermVector
objects.
For TermVectors
, files names will be numbered, one for each TermVector
.
For EigenElements
and SvdElements
, eigen/svd vectors will be managed as a TermVectors
. There will be an additional file with the suffix eigenvalues/singularvalues and extension .txt.
Example:
EigenElements ees=eigenSolve(...);
saveToFile("EES.vtu", ees); // generates EES_eigenvalues.txt and EES_1_Omega.vtu, EES_2_Omega.vtu, ...
As for Mesh
, you have an additionnal parameter to control if your save generates only one file (key _aFilePerDomain
without value), or a file per domain (key _aFilePerDomain
). Thhe difference hhere is that the default value is a file per domain.
At last, instead of renaming a TermVector
(or TermVectors
, …), you can use the key _data_name
in saveToFile
Save a TermMatrix
#
For a TermMatrix
, keys are different. Here, you have the key _storage
to set the storage in which the matrix will be saved : coo
for coordinate format (the Matlab sparse format) or dense
format. Dedicated extension is respectively .coo and .txt.
You also have the ability to encode file names by adding a suffix built from unknown, test function (key _encodingFilename
) or not (key _noEncodingFilename
, the default).
Integral representation#
In the context of integral equation, the solution of IE is a potential on the boundary ( \(\Gamma\) ). This potential is not easy to interpret, so the final step of a BEM is often the reconstruction of the field outside \(\Gamma\). For instance, the Helmholtz diffraction Dirichlet problem may be solved using a single layer potential \(q = \left[ \partial_n u \right]_{\Gamma}\) and the diffracted field outside the boundary \(\Gamma\) is given by
XLiFE++ adresses the general form of integral representation:
where \(\DeclareMathOperator{\opk}{opk}\opk\) is an operator on kernel, \(\DeclareMathOperator{\opu}{opu}\opu\) an operator on unknown and \(\otimes\) one of the operation \(∗\), \(|\), \(^{\land}\) or \(\%\).
The previous example corresponds to \(opk=id,\; \; opu=id\) and \(\otimes = *\). To deal with such integral representation, the user has to define a linear form from intg()
constructor:
LinearForm ri=intg(Gamma, G*q); //default integration method
LinearForm ri=intg(Gamma, G*q, _quad=GaussLegendre, _order=3);//specifying quadrature rule
IntegrationMethods ims(_quad=GaussLegendre, _order=10, _bound=1., _quad=GaussLegendre, _order=3);
LinearForm ri=intg(Gamma, G*q, _method=ims); //specifying 2 quadrature rules
In these expressions, \(\Gamma\) is a Domain
object, G a Kernel
object and q an Unknown
object.
Singular integration method is required if you intend to evaluate the integral representation at points close to the boundary \(\Gamma\).
The linear form may be a linear combination of intg:
IntegrationMethods ims(_quad=GaussLegendre, _order=10, _bound=1., _quad=GaussLegendre, _order=3);
LinearForm ri=intg(Gamma, G*q, _method=ims) + intg(Gamma, (grad_x(G)|_nx)*q, _method=ims)
There are several methods to compute the integral representation.
Direct method#
To effectively compute integral representation you have to specify the vector representing the numerical potential,
a TermVector
object (say Q) and the points where to evaluate it. There are many way to give points:
-
compute at one point x:
LinearForm ri=intg(Gamma, G*q, _quad=GaussLegendre, _order=3); Vector<Point> xs(10); xs(1)= Point(0,0,2); ... Vector<Complex> val(10); integralRepresentation(xs, ri, Q, val);
-
compute at an implicit list of points of a
Domain
object (say omega):LinearForm ri=intg(Gamma, G*q, _quad=GaussLegendre, _order=3); Vector<Point> xs; Vector<Complex> val; integralRepresentation(omega, ri, Q, val, xs); // val and xs are filled by function
-
compute at an implicit list of node points of an interpolation on a
Domain
:LinearForm ri=intg(Gamma, G*q, _quad=GaussLegendre, _order=3); TermVector U=integralRepresentation(u, omega, ri, Q); //u unknown on a Lagrange Space
Hint
In the previous syntaxes, the type of output val
has to be consistent with data.
For instance, val
is of complex type if G
or Q
is of complex type.
The last syntax is more robust because the type is determined by the function.
Besides, this syntax returns a TermVector
that may be straight exported to a file for visualization.
Note that, integral representations may return a vector of vectors but not a vector of matrices. For instance, if you want to compute the gradient of the integral representation (scalar), write:
Vector<Vector<Complex> > gsl;
integralRepresentation(xs, intg(Gamma, grad_x(K)*u, _method=ims), dnU, gsl);
or
Unknown us(V, _name="us", _dim=2); //vector unknown !
TermVector gSL=integralRepresentation(us, omega, intg(Gamma,grad_x(K)*u, _method=ims), dnU);
In this last form, attach to your TermVector
a vector unknown with the dimension of the result.
Matrix method#
There exists another way to deal with integral representations. It consists in computing the matrix
Special functions will produce such matrices embedded in a TermMatrix
object:
TermMatrix integralRepresentation(const Unknown&, const GeomDomain&, const LinearForm&);
TermMatrix integralRepresentation(const GeomDomain&, const LinearForm&);
TermMatrix integralRepresentation(const std::vector<Point>&, const LinearForm&);
When no domain is explicitly passed, one shadow PointsDomain
object will be created from the list of points given.
When no unknown is explicitly passed, one (minimal) space and related shadow unknown will be created to represent the row unknown.
The following example (2D diffraction problem) shows how to use this method of integral representation:
Number nmesh=25;
Disk disk_int(_center=Point(0.,0.,0.), _radius=1., _nnodes=nmesh, _side_names="Gamma");
Disk disk_ext(_center=Point(0.,0.,0.), _radius=2., _nnodes=2*nmesh, _domain_name="Omega", _side_names="Sigma");
Mesh mesh(disk_ext-disk_int, _generator=gmsh);
Domain Gamma = mesh.domain("Gamma"), Sigma = mesh.domain("Sigma"), Omega=mesh.domain("Omega");
// define spaces, unknown and testfunction
Space V0(_domain=Gamma, _interpolation=P0, _name="V0", _notOptimizeNumbering);
Unknown u0(V0,"u0"); TestFunction v0(u0,"v0");
Space V1(_domain=Omega, _interpolation=P1, _name="V1", _notOptimizeNumbering);
Unknown u(V1,"u");
//define Kernel and integration method
Kernel H=Helmholtz3dKernel(k);
IntegrationMethods ims(_method=Duffy, _order=8, _bound=0., _quad=GaussLegendre, _order=6, _bound=2., _quad=GaussLegendre, _order=3);
//define forms
LinearForm lf = intg(Gamma, g*v0); //rhs linear form
BilinearForm aSL=intg(Gamma, Gamma, u0*H*v0, _method=ims); //single layer bininear form
LinearForm lSL(intg(Gamma, H*u0, _quad=GaussLegendre, _order=6)); //intg. rep. linear form
//build system and solve it
TermVector rhs(lf);
TermMatrix ASL(aSL);
TermVector uSL = gmresSolve(ASL, rhs, _tolerance=1.0e-6, _maxIt=500);
//intg rep on Omega producing a TermMatrix
TermMatrix R=integralRepresentation(u, Omega, lSL);
TermVector U=R*uSL;
This method is a little more time expensive than computing directly the integral representation. Thus, if there are a lot of integral representations to do with different data, it may be of interest. Obviously, it is memory consuming.
Kernel interpolation method#
When points \(x\) are far from boundary \(\Gamma\), an alternate method consists in computing IR by interpolation method. Let \(\Omega\) the domain where IR is evaluated and \(V_{\Omega}\) a Lagrange finite element space of interpolation defined on \(\Omega\). Denote \((w_i)_i\) the basis functions associated to \(V_{\Omega}\) space. Let \(W_{\Gamma}\) a Lagrange finite element space defined on \(\Gamma\) and \((\tau_j)_j\) the basis functions associated to it. Interpolated the kernel at nodes \(x_i \in \Omega\) and \(y_j \in \Gamma\), IR is approximated by
If \(q\) has the following decomposition \(q(y)=\sum_k q_k \sigma_k (y)\) we have :
that reads in vector form \(\left( U = (u(x_i)_i),\;\; Q = (q(x_k)_k)\right)\):
with \(\mathbb G\) the matrix \(\left( G(x_i, y_j) \right)_{ij}\) and \(\mathbb M\) the mass matrix:
This example shows how it is done with XLiFE++:
Space Vq(_domain=Omega, _interpolation=P0); Unknown q(Vq,"q");
//computation of Q ...
Space Vo(_domain=Omega, _interpolation=P1); Unknown u(Vo,"u");
Space Vg(_domain=Gamma, _interpolation=P1); Unknown v(Vg,"v");
TermMatrix Gi(u, Omega, v, Gamma, G, _name="Gi"); //G(xi,yj)
TermMatrix M(intg(Gamma, q*v), _name="M");
TermVector U=Gi*(M*Q);
Because kernel is interpolated, the mesh of \(\Omega\) does not be too coarse. \(Vg\) may be chosen equal to \(Vq.\) This method is generally faster than previous ones because computation of the mass matrix is a fast process, but interpolation method fails at points \(x_j\) too close to the boundary \(\Gamma\).
Visualization#
By itself, XLiFE++ is a finite element library and as such does not own any graphical possibilities. At present, three main software products are targeted through the ouput formats mentionned in the previous section.
From a practical point of view, in order to obtain a graphical representation of the computation result,
one has to process the output file produced by the saveToFile
command by the corresponding software.
Minimal knowledge is required in order to use Gmsh and Paraview.
We invite the user to refer to the documentation of these software products.
Most of the computation results shown is this documentation are produced by Paraview and most of the meshes are displayed using Gmsh.
The Matlab/Octave format is intended to be (easily) used as follows.
We assume we have a .m script file, say eigs_1_Omega.m, containing the first vector computed and related to the domain Omega
(maybe an eigenvector or the solution to a linear system, stored as a TermVector
object in both cases).
One has to launch Matlab or Octave, eventually change to the directory containing the .m file and type in eigs_1_Omega at the prompt
(the execution of the script can also be achieved using the menus if the GUI is available).
We thus automatically get several figures, one showing the mesh based on the interpolation nodes,
and one for each component of each unknown.
The user is then free to make further computations using the data present in memory (see below) or modify the attributes of the figures.
The curves gathered on the figure Fig. 55 are obtained via this procedure. The corresponding mesh is :
Another example with a 2D geometry is shown on this figure:
The script is not interactive except for volumic data: the visualization is made by slicing the 3D domain with a plane and the user is invited to choose the cutting plane defined by a point and a vector normal to the plane, shown by a red thick line on the mesh figure. The position of the cutting plane is updated on this figure as its definition changes and the corresponding slices are displayed in other figures.
Let’s show what the command window looks like in such a case, here with Octave:
GNU Octave, version 4.0.3
Copyright (C) 2016 John W. Eaton and others.
This is free software; see the source code for copying conditions.
...
>> what
M-files in directory /tmp/demo/xlifepp:
eigs_1_Omega.m
>>
>> eigs_1_Omega
The intersection plane is defined by the point P and the orthogonal vector V.
Current value of P = 12.566 12.566 12.566
Current value of V = 1 -1 0
1 : change P
2 : change V
0 : quit
Your choice (other value = no change) : 3
Current value of P = 12.566 12.566 12.566
Current value of V = 1 -1 0
1 : change P
2 : change V
0 : quit
Your choice (other value = no change) : 0
Tuning suggestions:
figure(N)
subplot(2,2,k)
rotate3d, grid, box
view(2), view(3), view([Nx Ny Nz])
shading faceted
axis equal, axis normal
set(gca,"xtick",[]) or set(gca,"xtick",[...])
caxis([...])
xlabel(...), ylabel(...), title(...)
print("-dpng",eigs_1_OmegaFig2.png)
>>
Before the user types in 3 to answer the first question, the figure showing the domain mesh and an initial cutting plane has been displayed. By choosing the value 3, the user accepts the current settings and the figure representing the field is drawn. The user then terminates by answering 0, and some hints to modify the aspect of the figures are displayed.
When the script has terminated, we can observe the variables present in the workspace:
>> whos
Variables in the current scope:
Attr Name Size Bytes Class
==== ==== ==== ===== =====
coord 5859x3 140616 double
domaindim 1x1 8 double
domainname 1x5 5 char
elem 4832x8 309248 double
elemtype 4832x1 38656 double
interpDeg 1x1 8 double
spacedim 1x1 8 double
unknown 1x8 8 char
val 5859x1 46872 double
Total is 66940 elements using 535429 bytes
>> quit
This allows the user to make any processing of his own with these data. The definitions of the variables are the following:
coord
(real) Coordinates of the interpolation nodes, one node per row. The nodes are implicitly numbered from 1 tosize(coord,1)
.domaindim
(integer) Dimension of the domain (1, 2 or 3).domainname
(string) Name of the domain.elem
(integer) Array containing the lists of elements:elem(i,:)
is an element of typeelemtype(i)
. Format of the array: one element per row, column i holds the i-th interpolation node, given by its number in the array coord above.elemtype
(integer) Vector containing the type of each element present in the mesh, in the same order as the array elem above. Each type is a code number in XLiFE++’s internal codification: 2 = point, 3 = segment, 4 = triangle, 5 = quadrangle, 6 = tetrahedron, 7 = hexahedron, 8 = prism, 9 = pyramid.interpDeg
(integer) Interpolation degree used during the computation.spacedim
(integer) Dimension of the space (1, 2 or 3).unknown
(string) 1-column array containing the name of the unknowns, or their components in the vector case, one name per row.val
(real or complex) Values corresponding to the unknowns, stored column-wise, one column per component unknown. Each row contains the value at a node, or in an element if the interpolation degree is 0, in the same order as the array coord or elem respectively. Indeed, the graphical function ’patch’ used, makes the correspondence according to the number of rows of this array, which should besize(coord,1)
orsize(elem,1)
respectively.rootfn
(string) Filename of the calling script.
The variables domaindim
, domainname
, interpDeg
and spacedim
are defined for information
or consistency check purpose. Indeed, spacedim
should equal size(coord,2) and domaindim
should be less
or equal spacedim and be consistent with the element types.