MeshWorker clarifications

62 views
Skip to first unread message

Franco Milicchio

unread,
Mar 6, 2017, 9:52:05 AM3/6/17
to deal.II User Group
Dear all, 

I am trying to port the previous linear elastic code to MeshWorker, but I have some questions about this class. 

First of all, I have read this question, and I read that MeshWorker is sort of abandoned: 

the issue with MeshWorker is that when it was implemented, we didn't write enough documentation/tutorial programs/examples [...]. Development then also stopped, and so [...] nobody today knows any more whether they can or can not be done with MeshWorker. The only person who still knows how this all works unfortunately 
also doesn't regularly participate in the help forum any more :-( 

[...] you can of course ask questions about how this or that can be done, but you're likely not going to get a lot of helpful answers because nobody knows any more how this works. It's really an unfortunate situation we got ourselves into with this small corner of the library. 

So my first question is, should I avoid using this class and implement parallel loops by hand (via TBB or other means)?

Then, my doubts about the use of MeshWorkers. I cannot really grasp it even after reading tutorials 12, 16, and 39.

What is the purpose of the cell, boundary, and face functions? The cell is called exactly on each n-dimensional cell, I've checked that number, what about the other two? Boundary is called, as I read, for each n-dimensional cell on the boundary (numbers again match). Face function is called on each internal (n-1)-dimensional face (not on the boundary), as far as I understand. Is this correct?

When assigning a Neumann condition on faces, for instance in elasticity apply a load, where is the correct place? I have tried to get the code into the cell function, but I didn't find a way to get the faces, boundary markers, and the face values. My code is at the bottom, if you need it.

Is there an analogous of the FullMatrix for the right hand side vector? The only thing similar to a vector in the DoFInfo is a BlockVector...

Thanks for any clarification you can give me.

Cheers!

// Integrate local cell matrix

void integrate_cell_term(MeshWorker::DoFInfo<dim> &dinfo, MeshWorker::IntegrationInfo<dim> &info)

{    

    loopc++;

    

    // FE Values

    const FEValuesBase<dim> &fe_v = info.fe_values();

    

    // Local Lame values, here are stupid constants

    std::vector<double> lambda_values(fe_v.n_quadrature_points), mu_values(fe_v.n_quadrature_points);


    // Local element matrix

    FullMatrix<double> &local_matrix = dinfo.matrix(0).matrix;


    // Local element RHS vector

    BlockVector<double> &local_vector = dinfo.vector(0);

    

    // Jacobian

    const std::vector<double> &JxW = fe_v.get_JxW_values ();

    

    // Local Lame values

    lambda.value_list(fe_v.get_quadrature_points(), lambda_values);

    mu.value_list(fe_v.get_quadrature_points(), mu_values);

    

    // For each DOF of a cell (i-th)

    for (unsigned int i = 0; i < fe_v.dofs_per_cell; ++i)

    {

        const unsigned int component_i = info.fe_values().get_fe().system_to_component_index(i).first;


        // For each DOF of a cell (j-th)

        for (unsigned int j = 0; j < fe_v.dofs_per_cell; ++j)

        {

            const unsigned int component_j = info.fe_values().get_fe().system_to_component_index(j).first;


            // Compute the (i, j)-th component for LHS via quadrature

            for (unsigned int q_point = 0; q_point < fe_v.n_quadrature_points; ++q_point)

            {

                local_matrix(i,j) += ((fe_v.shape_grad(i, q_point)[component_i] *

                                       fe_v.shape_grad(j, q_point)[component_j] *

                                       lambda_values[q_point])

                                      +

                                      (fe_v.shape_grad(i, q_point)[component_j] *

                                       fe_v.shape_grad(j, q_point)[component_i] *

                                       mu_values[q_point])

                                      +

                                      (

                                       (i == j) ?

                                       (fe_v.shape_grad(component_i, q_point) *

                                        fe_v.shape_grad(component_j, q_point) *

                                        mu_values[q_point])

                                       : 0)

                                      ) * JxW[q_point];

            } // Integral for bilinear form

        } // j

        

        // Computed values for the RHS

        std::vector<Vector<double>> rhs_values(fe_v.n_quadrature_points, Vector<double>(dim));

        

        // Computed values for the RHS

        std::vector<Vector<double>> rhs_face_values(fe_v.n_quadrature_points, Vector<double>(dim));

        

        // RHS values

        right_hand_side.vector_value_list(fe_v.get_quadrature_points(), rhs_values);


        // Compute the i-th component for RHS via quadrature (body forces)

        for(unsigned int q_point = 0; q_point < fe_v.n_quadrature_points; ++q_point)

        {

            local_vector(i) +=  fe_v.shape_value(i, q_point) *

                                rhs_values[q_point](component_i) *

                                JxW[q_point];

        } // Integral for linear form


    } // i

    

    // Computed values for the RHS on the faces

    std::vector<Vector<double>> rhs_face_values(fe_v.n_quadrature_points, Vector<double>(dim));

    

    // Neumann (boundary forces on top boundary == id 2)

    for (unsigned int face_number = 0; face_number < GeometryInfo<dim>::faces_per_cell; ++face_number)

    {

        loopf++;

        

        if (dinfo.cell->face(face_number)->at_boundary() && (dinfo.cell->face(face_number)->boundary_id() == 2))

        {

            // FE Values on face (???)

            const FEValuesBase<dim> &fe_face_values = info.fe_values();

            

            // Get face FE values for this face and cell

            fe_face_values.reinit(dinfo.cell, face_number);

            

            // Neumann force vector on the face

            right_face_side.vector_value_list(fe_face_values.get_quadrature_points(), rhs_face_values);

            

            for(unsigned int f_q_point = 0; f_q_point < fe_v.n_quadrature_points; ++f_q_point)

            {

                // Add contributions to all DOFs

                for (unsigned int i = 0; i < fe_v.dofs_per_cell; ++i)

                {

                    const unsigned int component_i = fe_v.system_to_component_index(i).first;

                    

                    local_vector(i) += (fe_face_values.shape_value(i, f_q_point) *

                                        rhs_face_values[f_q_point][component_i]) * fe_face_values.JxW(f_q_point);

                    

                }

            } // Integral for linear form

        }

    } // Boundary forces


}


Daniel Arndt

unread,
Mar 6, 2017, 1:49:53 PM3/6/17
to deal.II User Group
Franco,


First of all, I have read this question, and I read that MeshWorker is sort of abandoned: 

the issue with MeshWorker is that when it was implemented, we didn't write enough documentation/tutorial programs/examples [...]. Development then also stopped, and so [...] nobody today knows any more whether they can or can not be done with MeshWorker. The only person who still knows how this all works unfortunately 
also doesn't regularly participate in the help forum any more :-( 

[...] you can of course ask questions about how this or that can be done, but you're likely not going to get a lot of helpful answers because nobody knows any more how this works. It's really an unfortunate situation we got ourselves into with this small corner of the library.

So my first question is, should I avoid using this class and implement parallel loops by hand (via TBB or other means)?
"amandus"[1] is in fact based on MeshWorker. If you are trying to do something similar to what is in the examples there, you have a good chance to be successful.
The problem really is the documentation, but with working code you should be able to understand the concepts.
 
Then, my doubts about the use of MeshWorkers. I cannot really grasp it even after reading tutorials 12, 16, and 39.

What is the purpose of the cell, boundary, and face functions? The cell is called exactly on each n-dimensional cell, I've checked that number, what about the other two? Boundary is called, as I read, for each n-dimensional cell on the boundary (numbers again match). Face function is called on each internal (n-1)-dimensional face (not on the boundary), as far as I understand. Is this correct?
In MeshWorker, you are basically only writing the assembly loops for your bilinear forms. You don't have to care about how to handle hanging nodes and the like.
In a typical DG formulation your bilinear form consists of a cell term, a boundary term and face term. These relate to the cell, boundary and face functions you are talking about. You should be able to see this in step-39.
 
When assigning a Neumann condition on faces, for instance in elasticity apply a load, where is the correct place? I have tried to get the code into the cell function, but I didn't find a way to get the faces, boundary markers, and the face values. My code is at the bottom, if you need it.
In general, your code fragment looks good, but the right-hand side should be handled in a different integrator object similar to what is done in step-39.
Neumann boundary conditions are normally considered in a weak sense and incorporated as boundary term into the bilinear form. So this should be in the boundary method of your MatrixIntegrator.
There is an elasticity example in amandus. Maybe this is useful.
 
Is there an analogous of the FullMatrix for the right hand side vector? The only thing similar to a vector in the DoFInfo is a BlockVector...
What exactly do you mean? In general, all vectors are considered to be block vectors with only one block that you can access via "dinfo.vector(0).block(0);".
If you initialize the "MeshWorker::DoFInfo" object with a "BlockInfo" object, you also have access to individual blocks. The structure then resembles the structure of your FiniteElement.

Franco Milicchio

unread,
Mar 7, 2017, 6:36:12 AM3/7/17
to deal.II User Group
Thanks for the answer, Daniel.


On Monday, March 6, 2017 at 7:49:53 PM UTC+1, Daniel Arndt wrote:

So my first question is, should I avoid using this class and implement parallel loops by hand (via TBB or other means)?
 
"amandus"[1] is in fact based on MeshWorker. If you are trying to do something similar to what is in the examples there, you have a good chance to be successful.
The problem really is the documentation, but with working code you should be able to understand the concepts.
 

Yes, I understand the concept, and I appreciate the code you posted, it should be really useful.
 
Then, my doubts about the use of MeshWorkers. I cannot really grasp it even after reading tutorials 12, 16, and 39.

What is the purpose of the cell, boundary, and face functions? The cell is called exactly on each n-dimensional cell, I've checked that number, what about the other two? Boundary is called, as I read, for each n-dimensional cell on the boundary (numbers again match). Face function is called on each internal (n-1)-dimensional face (not on the boundary), as far as I understand. Is this correct?
 
In MeshWorker, you are basically only writing the assembly loops for your bilinear forms. You don't have to care about how to handle hanging nodes and the like.

That's good, but in the same loop I could assemble (one or more) bilinear form and (one or more) linear form for the RHS.
 
In a typical DG formulation your bilinear form consists of a cell term, a boundary term and face term. These relate to the cell, boundary and face functions you are talking about. You should be able to see this in step-39.

I have implemented both serial and parallel (MeshWorker) version of the same bilinear form, and I got two different matrices. And if I see it correctly, it's not just numerical noise. In mathematica, with matpar/matser imported with a text file, resulting from system_matrix.print(std::cout):

In[17]:= Norm[matpar - matser]

Out[17]= 2.1304 * 10^10

In[20]:= {Eigenvalues[matpar, 10], Eigenvalues[matser, 10]}

Out[20]= {
{1.14866 * 10^11, 1.14329 * 10^11, 
  1.13441 * 10^11, 1.1221 * 10^11, 
  1.1065 * 10^11, 1.1055 * 10^11, 
  1.10036 * 10^11, 1.09185 * 10^11, 
  1.08777 * 10^11, 
  1.08005 * 10^11}, 
{1.35992 * 10^11, 
  1.35304 * 10^11, 1.34166 * 10^11, 
  1.32589 * 10^11, 1.30589 * 10^11, 
  1.30502 * 10^11, 1.29842 * 10^11, 
  1.28749 * 10^11, 1.28188 * 10^11, 
  1.27236 * 10^11}}

In[21]:= {Eigenvalues[matpar, -10], Eigenvalues[matser, -10]}

Out[21]= {
{2.17343 * 10^9, 2.17343 * 10^9, 
  2.16945 * 10^9, 2.16945 * 10^9, 
  2.16728 * 10^9, 2.16728 * 10^9, 
  1.34634 * 10^9, 1.34634 * 10^9, 
  1.25769 * 10^9, 
  1.25769 * 10^9}, 
{4.5406 * 10^7, 
  3.14171 * 10^7, 1.95205 * 10^7, 
  1.69433 * 10^7, 1.00199 * 10^7, 
  5.97384 * 10^6, 1.747 * 10^6, 
  1.0382 * 10^6, 581593., 28031.9}}

The last two commands give the first 10 and last 10 eigenvalues. Shouldn't the two assemblers yield the very same matrix?

When assigning a Neumann condition on faces, for instance in elasticity apply a load, where is the correct place? I have tried to get the code into the cell function, but I didn't find a way to get the faces, boundary markers, and the face values. My code is at the bottom, if you need it.
 
In general, your code fragment looks good, but the right-hand side should be handled in a different integrator object similar to what is done in step-39.

Uh, ok, I'll use that. This could have an impact on the performances, though.
 
Neumann boundary conditions are normally considered in a weak sense and incorporated as boundary term into the bilinear form. So this should be in the boundary method of your MatrixIntegrator.
There is an elasticity example in amandus. Maybe this is useful.

Thanks!
 
Is there an analogous of the FullMatrix for the right hand side vector? The only thing similar to a vector in the DoFInfo is a BlockVector...
 
What exactly do you mean? In general, all vectors are considered to be block vectors with only one block that you can access via "dinfo.vector(0).block(0);".

Good info, thanks.
 
If you initialize the "MeshWorker::DoFInfo" object with a "BlockInfo" object, you also have access to individual blocks. The structure then resembles the structure of your FiniteElement.

I will try this as soon as possible.
 

Thanks for the code, I'm reading it right now.

Cheers! 

Franco Milicchio

unread,
Mar 7, 2017, 10:53:51 AM3/7/17
to deal.II User Group
I have implemented, as suggested by Daniel, another MeshWorker callback for the right hand side.

The code is at the bottom, but I don't know, given the status of the MeshWorker documentation, if this is correct. I cannot say this by looking at the solutions, since matices, as I said, differ greatly between serial and parallel parts.

Any hints on what I'm doing wrong with my implementation?

Cheers!


static void rhs_integrate_boundary_term (MeshWorker::DoFInfo<dim> &dinfo, MeshWorker::IntegrationInfo<dim> &info)

{

    if (dumpall)

    {

        std::lock_guard<std::mutex> guard(lock);

        std::cout << "> RHS boundary " << dinfo.cell->id() << " face " << dinfo.face_number << std::endl;

   

    }

    loopb++;

    

    // FE Values

    const FEValuesBase<dim> &fe_v = info.fe_values();

    

    // Local RHS vector

    Vector<double> &local_vector = dinfo.vector(0).block(0);

    

    // Local values

    std::vector<Vector<double>> boundary_values(fe_v.n_quadrature_points, Vector<double>(dim));

    

    // Compute values for this boundary cell

    right_face_side.vector_value_list(fe_v.get_quadrature_points(), boundary_values);

    

    for (unsigned k = 0; k < fe_v.n_quadrature_points; ++k)

        for (unsigned int i = 0; i < fe_v.dofs_per_cell; ++i)

        {

            const unsigned int component_i = info.fe_values().get_fe().system_to_component_index(i).first;

            

            local_vector(i) += (fe_v.shape_value(i, k) * boundary_values[k][component_i]) * fe_v.JxW(k);

        }


}

Franco Milicchio

unread,
Mar 8, 2017, 7:04:01 AM3/8/17
to deal.II User Group
I have made available the snippet for this example here with the possibility of using either the MeshWorker or not (just change the #define).

As far as I see, the code is copied and pasted correctly.

The right hand sides are the same, but matrices differ.

Thanks for any pointers!
    Franco

Daniel Arndt

unread,
Mar 9, 2017, 1:33:16 PM3/9/17
to deal.II User Group
Franco,

on the first glance your code looks good. Can you reduce the size of your problem to a single cell?
Is there still a difference then?

Best,
Daniel

Franco Milicchio

unread,
Mar 10, 2017, 4:30:53 AM3/10/17
to deal.II User Group
Daniel,

I've run the problem with a simple GridGenerator::hyper_rectangle and the results differ. I'm filing a bug report for this.

The norm of the matrix difference is 10^11, but at least the sparsity pattern remains the same, if this could be of any consolation!

Should I look at something else?

Thanks!
   Franco

Franco Milicchio

unread,
Mar 10, 2017, 7:42:25 AM3/10/17
to deal.II User Group
It was all my mistake in copy/pasting the weak form. The two matrices now are the same.

Sorry for the noise.

Jean-Paul Pelteret

unread,
Mar 10, 2017, 8:29:01 AM3/10/17
to deal.II User Group
For the record, this contribution in the original post

((i == j) ?

 (fe_v.shape_grad(component_i, q_point) *

  fe_v.shape_grad(component_j, q_point) *

  mu_values[q_point]) :  

 0)


should have read

For the record, this contribution in the original post

((component_i == component_j) ?

 (fe_v.shape_grad(i, q_point) *

  fe_v.shape_grad(j, q_point) *

  mu_values[q_point]) :  

 0)

Franco Milicchio

unread,
Mar 10, 2017, 10:29:42 AM3/10/17
to deal.II User Group
Just one more question about MeshWorker.

I am trying to figure how I could assemble simultaneously more than one form. I've found that step 39 uses two different assemblers.

My guess is that I could use a MatrixSimple assembler for bilinear forms, and ResidualSimple for linear forms.

Is this the correct approach in deal.II?

Thanks!
   Franco
Reply all
Reply to author
Forward
0 new messages