This tutorial explains how to customise the evaluation of a singular integral in NiHu. Our example of demonstration is the collocational singular integral of the 3D Helmholtz single layer potential kernel on a constant triangle element.
Theory
The singular integral
The collocational singular integral of the single layer potential kernel on a constant triangular element reads as
\( \displaystyle L = \int_S \frac{\exp(-\mathrm{i} k r)}{4\pi r}\mathrm{d}S_{\bf y}, \quad r = |{\bf y} - {\bf x}_0| \)
where \( {\bf x}_0 \) is the singular collocation point in the center of the element.
The method of static part subtraction
The integral \( L \) can be regularised by subtracting and adding its static \( k = 0 \) part:
where \( R_i \) denotes the distance from the singular point to the \( i \)-th corner of the triangle, and the angles \( \phi_i \) and \( \alpha_i \) are explained in the figure below:
Definition of the radii and angles for the singular integral of the static kernel
The C++ code
The singular integral shortcut
Singular integrals can be customised by specialising the class template NiHu::singular_integral_shortcut for the specified singular integral type.
The class template is declared as
template <class Kernel, class TestField, class TrialField, class Singulartiy, class Enable>
class singular_integral_shortcut;
Where the parameters are
Kernel the kernel type (NiHu::helmholtz_3d_SLP_kernel template in our case)
TestField, TrialField the test and trial field types (both constant triangles, the test field is a Dirac-view because of the collocational approach)
Singularity the singularity type (NiHu::singularity::face_match_type in our case)
Enable an additional parameter to make complex type selection easy using the C++11 feature std::enable_if
field_base<TrialField> const &trial_field, // the trial field instance
element_match const &) // match data (unused for face_match)
{
// ...
}
//...
};
The test and trial field types are left as template arguments of the specialisation, but they are used in the arguments of std::enable_if to select the appropriate specialisation case. The specialisation is enabled if
the geometrical interpolation function (L-set) of the trial field is NiHu::tria_1_shape_set
the field interpolation function (N-set) of the trial field is NiHu::tria_0_shape_set
Note
the test field does not need to be taken into account, as the NiHu::singularity::face_match_type singulartiy implies that the test and trial element types are identical.
As shown above, the class specialisation defines a static public member function called eval that evaluates the singular integral into a result matrix received by reference as function argument.
Integrating the static part
First we define a helper function triangle_helper that computes the radii \( R_i \) and the angles \( \alpha_i \) and \( \theta_i \) for a triangle element.
void triangle_helper(
tria_1_elem const &elem,
double r[], // output radii
double theta[], // output angles
double alpha []) // output angles
{
auto const &C_old = elem.get_coords(); // element vertices in columns
auto const &x0 = elem.get_center(); // collocational point x0
typename tria_1_elem::coords_t R, C;
for (unsigned i = 0; i < 3; ++i)
{
R.col(i) = C_old.col(i) - x0; // vector from x0 to corner
r[i] = R.col(i).norm(); // distance
R.col(i) /= r[i]; // normalise vector
C.col(i) = C_old.col(i) - C_old.col((i+1) % 3); // side vector
return result; // only the input argument reference is returned
}
Integrating the dynamic part
For the numerical integration of the regular dynamic part, the class needs to store the quadrature points of a suitable regular quadrature. The following static member quadrature is suitable:
static gaussian_quadrature<tria_domain> const reg_quadrature(7) // 7-th order
Class NiHu::gaussian_quadrature<tria_domain> defines a Gaussian quadarture for a triangle domain. The constructor takes the integration order as parameter. The generated quadrature is able to integrate 7-th order polynomials on a triangle without error. The quadrature is implemented as a container of quadrature points, and provides an iterator that traverses the quadrature points. Using this quadrature, the dynamic part is integrated as
std::complex<double> I_dyn = 0.0;
auto const &x0 = elem.get_center(); // element center
double k = kernel.get_data().get_wave_number(); // wave number
// traverse quadrature points
for (auto it = reg_quadrature.begin(); it != reg_quadrature.end(); ++it)
{
double r = (elem.get_x(it->get_xi()) - x0).norm(); // distance from source