Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] wall_shear_stress_calculator for incompressible Navier--Stokes #349

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

richardschu
Copy link
Contributor

@richardschu richardschu commented Feb 22, 2023

Added a wall_shear_stress_calculator for the incompressible_navier_stokes solver. Interpolation via dealii::FEFaceValues on boundaries with specified IDs, on the entire boundary or skip (see Poiseuille flow example). Output via dealii::DataOutFaces (in 3D only, since .vtk output is not available for 2D). This is in the incompressible_navier_stokes module, since I assumed constant viscosity and density.
For an iliac bifurcation, this might look something like (nodal values on the inlet are zero):
Screenshot from 2023-02-22 17-26-03

{
unsigned int matching_idx = face_to_cell_index[face][i];

#ifdef DEBUG
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we even want to have this check here? ... could be useful if someone accidentally changes face orientation on the boundary at some point.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by "someone accidentally changes face orientation on the boundary"? A bug being introduced in deal.II?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::cout output is problematic in ExaDG sections guarded by debug, because this could cause failing tests when running in debug mode. However, our policy in ExaDG is that tests should pass in both debug and release mode.

For code review right now, I think it would be easier for me if all debug sections were removed and were realized experimentally on a developer branch for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone is manually changing the face orientation on the outer boundary, this influences the ordering of the points/DoFs -> so we would have non-matching points. @peterum commented on this, that this should never be the case, which is why I do the matching during initialize.

Regarding the debug sections and std::cout there:
do the checks compare the log files via diff?
If the face orientation is not changed on the boundary (by mistake?!), even debug mode does not output anything - it ONLY CHECKS, if the match was successful, but should still not output anything. So I think if the tests then fail here ... this actually tells us a lot just as we want, right?

Of course one could remove that entirely, but this is kind of a safety measure. We could also always match the points for each face, but that is considerably more effort. And that wall shear stress will lateron be needed for adapting boundary conditions, so it has to be computed at each time step!

So I would actually rather keep it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have seen somewhere std::cout, which is problematic in terms of testing.

that this should never be the case

What does "should" mean here? Is it checked by deal.II and it can be considered a bug on the deal.II side if it is the case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this std::cout is only reached, when someone accidentally switched the boundary face orientation.
With that "should" I was actually paraphrasing @peterrum ALTERNATIVELY, I could also just put an AssertThrow there, does that sound better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, makes sense (AssertThrow).

I still did not really understand where this "accident" happens.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I did that now.
So what we do in initialize is generate a face index to cell index map and later on, we can use this map to get indices from the face to the DoFs on the cell.

This assumes that face orientations do not change, and are in fact similar to the first cell encountered on each subdomain. Since I do not know enough regarding changes in orientation or when they might occur (maybe even by accident?), I put the initial node matching into the cell loop to be re-done for each cell all the time in debug mode and Assert if it does not match.

In release mode, I skip this match on each face integration point to the corresponding cell integration point.

@@ -867,6 +872,13 @@ SpatialOperatorBase<dim, Number>::get_viscosity() const
return param.viscosity;
}

template<int dim, typename Number>
double
SpatialOperatorBase<dim, Number>::get_density() const
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not need this in the end, should I remove it again?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, should be removed again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did :)

unsigned int quad_index;
std::vector<std::vector<dealii::types::global_dof_index>> face_to_cell_index;
double dynamic_viscosity;
double const rel_tol = 1.0e-5;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relative tolerance used for point matching combined with cell->diameter()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to add this as a comment to the code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment.

@nfehn nfehn added the new feature New feature is implemented or requested label Mar 6, 2023
pp_data.output_data.write_shear_rate = true;
pp_data.output_data.write_velocity_magnitude = true;
pp_data.output_data.write_vorticity_magnitude = true;
pp_data.output_data.write_wall_shear_stress_on_IDs = {dealii::numbers::invalid_boundary_id};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should implement it in a way such that this default initialization is not necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now, the default is: write on all boundaries, if write_wall_shear_stress == true, and the user can specify a std::set of boundary_ids. I would like this more than just allowing a single one, since we might use the boundary ID more actual varying boundary conditions on the wall (thrombus growth, heated surface or whatever - some multiphysics application). In my mind this is just a bit more flexible.

@@ -77,6 +78,9 @@ struct OutputData : public OutputDataBase
// write vorticity magnitude
bool write_vorticity_magnitude;

// write wall shear stress on IDs
std::vector<dealii::types::boundary_id> write_wall_shear_stress_on_IDs;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the name should include "boundary", e.g. _boundary_IDs or short _bids.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did, good point.

Comment on lines 40 to 41
double const kinematic_viscosity,
double const density)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not writing the interface with dynamic_viscosity?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it, this was for no special reason. I will actually rewrite this once we have a fixed setup with the variable viscosity!

Comment on lines 166 to 170
wall_shear_stress.evaluate(velocity);
if(dim == 3)
surface_fields_vtu.push_back(&wall_shear_stress);
else
additional_fields_vtu.push_back(&wall_shear_stress);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, I do not understand this. Does it mean that a volume field is written in 2D for the wall shear stress? This would not be intuitive and require some explanation in my opinion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, unfortunately. deal.II does not allow output of 2D SurfaceOutput. Hence I write a volumetric data. And yes, I agree that this is ugly =(

VectorType & dst,
VectorType const & src,
std::shared_ptr<dealii::Mapping<dim> const> mapping,
std::vector<dealii::types::boundary_id> const write_wall_shear_stress_on_IDs) const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also here include "boundary" in the name (see other comment).

@@ -200,6 +257,13 @@ OutputGenerator<dim, Number>::evaluate(
additional_fields,
time_control.get_counter(),
mpi_comm);

if(dim == 3 && surface_fields.size() > 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I currently think this if statement should appear inside the function write_surface_output() from a design perspective.


if(dim == 3 && surface_fields.size() > 0)
{
// surface vtk output for dim == 3 only
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to mention the "vtk" info here in my opinion.

Comment on lines 127 to 128
AssertThrow(dim == 3, dealii::ExcMessage("Surface vtk output for dim == 3 only."));
AssertThrow(surface_fields.size() > 0, dealii::ExcMessage("Provided surface fields empty."));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be removed when "if-statement" is implemented here.

Comment on lines 140 to 165
if(additional_field->get_type() == SolutionFieldType::scalar)
{
data_out.add_data_vector(additional_field->get_dof_handler(),
additional_field->get(),
additional_field->get_name());
}
else if(additional_field->get_type() == SolutionFieldType::cellwise)
{
data_out.add_data_vector(additional_field->get(), additional_field->get_name());
}
else if(additional_field->get_type() == SolutionFieldType::vector)
{
std::vector<std::string> names(dim, additional_field->get_name());
std::vector<dealii::DataComponentInterpretation::DataComponentInterpretation>
component_interpretation(dim,
dealii::DataComponentInterpretation::component_is_part_of_vector);

data_out.add_data_vector(additional_field->get_dof_handler(),
additional_field->get(),
names,
component_interpretation);
}
else
{
AssertThrow(false, dealii::ExcMessage("Not implemented."));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like copied code. What options do we have to avoid this?

I think there is a base class DataOutInterface in deal.II. Then, we could implement this functionality as a utility function in ExaDG with pointer to DataOutInterface as function argument. We might then need a dynamic cast to distinguish between DataOut and DataOutFaces inside the function, but I think this would still be better than code duplication.

@@ -186,6 +242,7 @@ OutputGenerator<dim, Number>::evaluate(
VectorType const & velocity,
VectorType const & pressure,
std::vector<dealii::SmartPointer<SolutionField<dim, Number>>> const & additional_fields,
std::vector<dealii::SmartPointer<SolutionField<dim, Number>>> const & surface_fields,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we currently have a mixture of designs. In some sense, this line breaks the modularity of the Navier-Stokes postprocessing design (we need to change interfaces to realize more/other functionality). According to the current function interface above, I would have expected that the new functionality is included in the existing write_output() function. Instead, a separate write_surface_output() function is introduced, which aims at a modular design somewhat deeper in the code. However, this raises the question why surface output is then not realized completely modular as a separate postprocessing module (e.g. SurfaceOutputGenerator, or a seperate function inside the existing OutputGenerator). In my opinion, both realizations are possible and should be discussed.

Some arguments below:

Another option could be to equip SolutionField with additional information (volume vs. surface) such that the if-branches can be made at the point where output is written. I am not sure if this makes the design better, because in the end, we also have to discuss the design in relation to the deal.II library (which has separate classes DataOut and DataOutFaces). This argument relates also to this discussion https://github.com/exadg/exadg/pull/349/files#r1126093917

What should be mentioned at this point and be part of the overall discussion is that I discussed with @c-p-schmidt some time ago that it would actually be better that write_output() would only receive additional_fields as argument and not velocity and pressure separately, because this would allow us to (maybe) use exactly the same write_output() function for all PDE modules in ExaDG (Poisson, ConvDiff, NavierStokes, etc.). This argument supports to keep write_output() and write_surface_output() separately (and separate arguments volume_fields and surface_fields), as is done currently.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether it works for all modules but it sounded very appealing to me. If this additional_fields data object consists of objects the final output routine is able to write to disc, then the write_output() method should mainly need to iterate over the provided data from additional_fields and choose the correct output method to write to disc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not passing in velocity and pressure (and hence not needing the two dof_handlers) and reusing the same write_output sounds quite elegant. This calls for some Calculators in navier_stokes_calculators then, right? I think adding the fields and then having surface and volume output separate in utilities or postprocessor would be good.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calculators should not be necessary, because velocity/pressure are the primary variables for IncNS. One just needs to add velocity/pressure to the vector of solution fields.

Comment on lines 205 to 210
dealii::Tensor<1, dim> const S_times_n =
dynamic_viscosity * (velocity_gradients[i] + transpose(velocity_gradients[i])) *
fe_face_values.normal_vector(i);
dealii::Tensor<1, dim> const wall_shear_stress =
S_times_n - scalar_product((S_times_n), fe_face_values.normal_vector(i)) *
fe_face_values.normal_vector(i);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would introduce

normal = fe_face_values.normal_vector(i);

since it is used three times here.

AssertThrow(
write_wall_shear_stress_on_IDs.size() > 0,
dealii::ExcMessage(
"Provide at least write_wall_shear_stress_on_IDs = {dealii::numbers::invalid_boundary_id}.")) bool
Copy link
Member

@nfehn nfehn Mar 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a formatting error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly do not know. It only does that when you forget the ";".
But it compiled just fine.
Anyways, I corrected this. Thanks!

Comment on lines 153 to 157
if(cell->is_locally_owned() == false)
continue;

if(cell->at_boundary() == false)
continue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I do not like the if(.... == false) continue; style (negations are always harder to read for humans than positive statements). I think this "style" originates from indentation aspects with 80 characters per line in deal.II and the fact that deal.II indentation introduces two tabs whenever using brackets. Note that all this does not apply for ExaDG.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I am fine with that style!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same style question: should I even write if( ... == true)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would skip ==true

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

passt, then I will adopt that style from now on!

Comment on lines 122 to 123
unsigned int const dofs_per_component =
matrix_free->get_dof_handler(dof_index).get_fe(0).dofs_per_cell / dim;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't deal.II provide the information about scalar dofs per cell directly? Where is this variable actually used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did not fine one in classFiniteElement, and did not get lucky with get_sub_fe(component_mask).
This form here works for all ReferenceCell types, so that should be fine as far as I am concerned?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For primitive elements (FESystem of FE_Q, FE_DGQ etc), you can use get_fe(0).base_element(0).dofs_per_cell. However, this does not work for FE_RaviartThomas if I remember correctly, as that is a monolithic block of 3 elements.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is your suggestion @kronbichler as the most general solution?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole approach realies heavily on having FE_DGQ (probably FE_Q are ok as well, since the dofs_per_cell are still the same I believe) -- with FE_RaviartThomas, however, this whole idea of matching GausLobattoPoints to DoFs goes down the drain anyways, right? One should at least assert that, right?

Comment on lines 118 to 120
std::map<dealii::types::boundary_id, bool> write_on_boundary_IDs;
for(auto const id : write_wall_shear_stress_on_IDs)
write_on_boundary_IDs.insert({id, true});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like std::set would be more appropriate?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not see the reason to introduce write_on_boundary_IDs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed to std::set, thanks, that spared me the std::map introduced here and from the user side its identical!

unsigned int const dofs_per_component =
matrix_free->get_dof_handler(dof_index).get_fe(0).dofs_per_cell / dim;
unsigned int const fe_degree = matrix_free->get_dof_handler(dof_index).get_fe(0).degree;
dealii::FESystem<dim> fe_system(dealii::FE_DGQ<dim>(fe_degree), dim);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to keep in mind (and assert) that this will not work for simplex.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did that for now. But one could also setup FE_DGSimplex and FE_DGQ and use them depending on is_hyper_cube. Problem is: I do not want to introduce functionality I have not tested, I do not want to introduce a temporary test now, and simplices are not yet ready for iNS.
Once we have that, we can refactor without a big hastle and test in one go!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of the Assert in the beginning, we could put the Assert in the location where the code will fail, so that it is easier to extend the code in the future. Just an opinion, it is also fine if it stays at the beginning. I just wanted to jump into the discussion after seeing the word simplex.

Comment on lines 112 to 117
write_on_all_boundary_IDs = false;
if(write_wall_shear_stress_on_IDs.size() == 1)
{
if(write_wall_shear_stress_on_IDs[0] == dealii::numbers::invalid_boundary_id)
write_on_all_boundary_IDs = true;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am against an implicit logic as realized here. I think this clarifies my original comment here https://github.com/exadg/exadg/pull/349/files#r1126058363. If we want to write on all boundaries, I think we have to list all of them in the application file.

From an application perspective, I would argue that no-slip wall boundaries (also referring to the picture you show in this PR) typically have the same boundary ID (the deal.II default value 0)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went for default all based on write_wall_shear_stress, or selected ones when IDs are specified, is that ok for you as well?

VectorType & dst,
VectorType const & src,
std::shared_ptr<dealii::Mapping<dim> const> mapping,
std::vector<dealii::types::boundary_id> const write_wall_shear_stress_on_IDs) const
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::set?

@richardschu richardschu changed the title wall_shear_stress_calculator for incompressible Navier--Stokes [WIP] wall_shear_stress_calculator for incompressible Navier--Stokes Mar 7, 2023
@richardschu richardschu marked this pull request as draft March 7, 2023 15:51
Comment on lines +149 to +150
dealii::FEFaceValues<dim> fe_face_values(
*mapping, fe_system, face_quadrature, dealii::update_gradients | dealii::update_normal_vectors);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this function. Why do you use a newly created FESystem instead of using matrix_free->get_dof_handler(dof_index).get_fe(0)? Isn't this leading to an error in get_function_gradients(), because the FiniteElement used here does not match the FiniteElement in matrix_free->get_dof_handler(dof_index).active_cell_iterators()? I would have written

Suggested change
dealii::FEFaceValues<dim> fe_face_values(
*mapping, fe_system, face_quadrature, dealii::update_gradients | dealii::update_normal_vectors);
dealii::FEFaceValues<dim> fe_face_values(
*mapping, matrix_free->get_dof_handler(dof_index).get_fe(), face_quadrature, dealii::update_gradients | dealii::update_normal_vectors);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was not leading to an error, since I only used 'FE_DGQ', I did not test with other element types, which is of course very restrictive - I will address this in the other comment section.

Comment on lines +210 to +215
for(unsigned int d = 0; d < dim; ++d)
{
if(src.get_partitioner()->in_local_range(
dof_indices[matching_idx + d * dofs_per_component]))
dst(dof_indices[matching_idx + d * dofs_per_component]) = wall_shear_stress[d];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will only work for discontinuous elements such as FESystem(FE_DGQ) or FESystem(FE_SimplexDGP), which is something you cannot control here. The reason is that deal.II orders unknowns entity-by-entity, i.e., first all vertices (in their order within a cell), then lines, then faces (aka 'quads'), then volumes (aka 'hexes', this naming also applies to tetrahedra). For discontinuous elements, this ends up being the expected ordering, because all DoFs are associated with the volume, but not for continuous elements where they (necessarily) live on vertices, lines, etc. - so overall you make assumptions on how unknowns are ordered.

Without full knowledge of the code (and all comments, sorry for that), I think that this is a fundamental issue on how this code is structured. As this is post-processing of a quantity that only lives on the boundary, the first question would be whether DataOutFaces could have been used (generating a different file). If output in the same file would be desired, the approach I would have taken is to solve a least-squares system that tries to make the polynomial solution representation of a discontinuous solution space (irrespective the underlying FiniteElement) directly in the output module. This means that I would intuitively not create a global vector first and then write it somewhere, but use the DataPostprocessor facilities of deal.II for that, see https://www.dealii.org/developer/doxygen/deal.II/classDataPostprocessor.html
Even though there might be some extensions (or performance optimizations) necessary in that module, the overall feeling is that we should not duplicate such functionality in ExaDG unless there are good reasons.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so maybe let me give some background to find the best solution for the actual thing I am interested in:

the goal is to integrate the wall shear stress in time, such that a global vector containing the DoFs seems most suitable from my point of view (easy timestepping in a postprocessor, #379). Ideally, such a vector would only include DoFs on the boundary, but generating separate data structures associated with such a change of view seems cumbersome? The entries in this DoF vector contain the traction (contains derivatives of volumetric shape functions, but also the normals are needed). For flexibility, a projection on the boundary would indeed be the best option here, such that we have a DGQ or DG_Simplex target ansatz and test space we project whatever the sought quantity is on the boundary only. This actually translates to the FSI case as well, where the interface traction is very much similar. Reducing the Vector size in the quasi-Newton schemes might help as well (at least reduces the effort spent in the individual steps of such schemes, especially since those are sometimes high in complexity! #379)

This strategy calls for: i) DoFs on the boundary active only and ii) access to volumetric functions and their derivatives.

If we were only interested in the output -- which you could not know prior to this comment that we are actually not -- I would maybe go for a 'DataPostprocessor'.

We could also have this as a prototype for "project function of values and gradients of some volumetric solution onto some boundary with somewhat arbitrary start (= volume) and target (=boundary) spaces". In my PhD project I did something similar (but the space on the boundary being just the trace of the volume space), but that already required two DoFHandlers, cumbersome index mapping and sorting degrees of freedom to get a blocked vector of interface DoFs.

So I would rather suggest keeping a volumetric vector and having kind of a boundary face-mask (if one wants to use it further) or just output the surface only.

This is in fact separate from "how is the vector actually computed?" - where I think a projection is the most elegant way, but the cell-wise inverse did not give me good results here (I implemented that first). I think the reason lies in the space used for the projection being somewhat too big: we have a function on the boundary only - imagine it were only a function of the solution value (not its gradient combined with the normal as is the case for the traction) - so, with a straight-edged element, and depending on the FE basis, some FE functions might not even contribute. Thus, they could be chosen freely and thus lead to ill-conditioning of the system, right?

All in all, this is one very specialized way of doing it - a fast solution to introducing these vectors. Turns out they could be used in more places as I laid out. I will transfer these points to #379 .
I will use it for now in my personal code, since I need results. We should settle the other discussion #379 first and later we can use this as a first testcase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new feature New feature is implemented or requested
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants