Skip to content

Linear Raviart-Thomas Finite Elements#3638

Merged
roystgnr merged 19 commits intolibMesh:develfrom
farscape-project:rt
Sep 12, 2023
Merged

Linear Raviart-Thomas Finite Elements#3638
roystgnr merged 19 commits intolibMesh:develfrom
farscape-project:rt

Conversation

@nmnobre
Copy link
Copy Markdown
Member

@nmnobre nmnobre commented Sep 8, 2023

Hi @roystgnr, @jwpeterson, @lindsayad,

Christmas came earlier this year! :)
Hopefully it's not too hard to review.

Includes Raviart-Thomas basis functions for 2d and 3d, Piola mappings for H(div), an example that tests the functionality in both 2d and 3d for all supported element types (TRI6/7, QUAD8/9, and TET14 and HEX27), and a dump of the example output for the html docs.

We do have the required additions to MOOSE ready to go as well as a standalone app which tests the functionality. We're just working on tidying things up and in bringing the example under one of the MOOSE modules, most likely Navier-Stokes or Electromagnetics. We also noticed TET14s are missing from MOOSE but, at least at first sight, it doesn't look like that would be a massive amount of work?

Lastly, we do have plans (and funding) to do a bit more with libMesh and MOOSE beyond Raviart-Thomas. It'd be great if we could have a chat about these... it seems it'd be quite a lot of work, so we'd like to better understand the required effort, but also to get your opinion on how relevant these new features would be, how these align with your vision for the future of libMesh/MOOSE, etc....

Cheers,
Nuno and Karthik (cc @karthichockalingam)

@moosebuild
Copy link
Copy Markdown

moosebuild commented Sep 8, 2023

Job Coverage on 8bd7b33 wanted to post the following:

Coverage

4b0b9e #3638 8bd7b3
Total Total +/- New
Rate 61.92% 61.95% +0.03% 65.43%
Hits 66897 67382 +485 477
Misses 41148 41392 +244 252

Diff coverage report

Full coverage report

Warnings

  • New new line coverage rate 65.43% is less than the suggested 90.0%

This comment will be updated on new commits.

@roystgnr
Copy link
Copy Markdown
Member

roystgnr commented Sep 8, 2023

I'm in between chores and errands on my day off, and I probably won't get to a review here until Monday, but let me at least immediately say: Thank you!!! Adding H(div) and Raviart-Thomas have been something in the "on my todo list but getting buried lower faster than it bubbles higher" category for ... ouch, literally a decade now. This is wonderful to see.

@roystgnr
Copy link
Copy Markdown
Member

roystgnr commented Sep 10, 2023

You copied an 11-year-old typo from one of @pbauman's Nedelec code comments. I'd have preferred it if you'd kept re-bootstrap results in separate commits. Unit test coverage would have been nice (just like it would have been for the other vector elements I decided not to cover when I was writing fe_test.h...) But I'm not seeing anything here yet to really criticize.

I'd like to play around a bit with this tomorrow afternoon, but if anyone else hits approve before then there's no need to wait on me.

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 11, 2023

You copied an 11-year-old typo from one of @pbauman's Nedelec code comments.

I caught a few, let me know if I tackled the one you spotted. :)

I'd have preferred it if you'd kept re-bootstrap results in separate commits.

Done.

I'd like to play around a bit with this tomorrow afternoon

Please do! :)

@jwpeterson
Copy link
Copy Markdown
Member

Not sure whether he has the time, but it would be valuable to get @pbauman's input on this PR as well given all the prior work that he did in adding vector-valued FE to libmesh.

Copy link
Copy Markdown
Member

@jwpeterson jwpeterson left a comment

Choose a reason for hiding this comment

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

I'm not really qualified to comment on the FEM aspects here, but I definitely appreciate that you've added an example (including the associated html documentation!) and this doesn't appear to break any of our existing tests, so definitely 💯% approval there. Thanks for this very nice contribution!

Comment thread examples/vector_fe/vector_fe_ex6/solution_function.h Outdated
@pbauman
Copy link
Copy Markdown
Member

pbauman commented Sep 11, 2023

Didn't really have time to check the math in detail, but the structure all seems to jive with what I remember. The main testing I'd done with Nedelec was to make sure I achieved the correct convergence rates in the different norms, so I would suggest the same be done with the example in the PR, namely for the example problem check the convergence rates of L2 and HDiv norms to the exact solution and make sure the expected rates are achieved.

Definitely mimic @roystgnr's sentiment. Was always on my TODO list and never got there. :( So happy to someone pick that up and add it.

@pbauman
Copy link
Copy Markdown
Member

pbauman commented Sep 11, 2023

Lastly, we do have plans (and funding) to do a bit more with libMesh and MOOSE beyond Raviart-Thomas.

Would love to AMR and Dirichlet BCs get properly tackled for these and Nedelec. It was indeed a lot of work, esp. handling 2D vs. 3D, was going to be a real PITA IIRC.

@roystgnr
Copy link
Copy Markdown
Member

I caught a few, let me know if I tackled the one you spotted. :)

Both the one I spotted, and some more egregious ones I missed. :-P Thanks!

@roystgnr
Copy link
Copy Markdown
Member

The TET14 case in the new vector_fe_ex6 is IMHO too slow in dbg mode: 2 minutes for me to run in serial. It's fine in devel (20s) and opt (15s), but we do run CI (and make check by hand) with dbg often enough that I don't want anything too expensive to add up. This is mostly an artifact of how we do build_cube with tets, I assume - subdivision into 24 tets means we don't have to worry about asymmetric diagonal selection on quad faces, but it also means that a 3.4K elem hex mesh turns into 80K tets; with RT0 here we go from like 14K DoFs to 240K. grid_size=6 gets us down to 16K tet DoFs, which finishes in under 10s in dbg mode, a fraction of a second in opt, which I think is about right.

It was also a bit annoying to play with that; rather than forcing some parameters to be loaded only from input file and others only from command line, why not just have the same GetPot object read the input file and then override on the command line?

I've pushed some commits for those things to my fork of your branch, if you want to look at them.

I'm happy with Paul's "check the convergence rates" test plan for now. Results:

L2 errors

elem grid=2 grid=4 grid=8 grid=16 grid=32
Tri 1.030 0.518 0.262 0.132 0.066
Quad 1.062 0.525 0.268 0.136 0.069
Hex 1.697 0.888 0.454 0.230 0.116
Tet 1.000 0.509 0.255 0.128 0.064

HDiv semi-norm errors

elem grid=2 grid=4 grid=8 grid=16 grid=32
Tri 2.411 1.343 0.741 0.424 0.257
Quad 2.890 1.622 0.873 0.479 0.276
Hex 5.071 2.862 1.524 0.815 0.451
Tet 2.506 1.347 0.736 0.420 0.253

The H(div) errors look roughly like what I'd have expected, but not quite. Shouldn't we be leveling off closer to C*h by the time we have h=1/32? 50% of the error after each subdivision, not 55%? And that 55-ish% seems pretty consistent through the table, there's not some obvious higher-order terms that are disappearing.

I'm afraid I'm ignorant of what I should expect in L2. Do we only expect O(h) here, not O(h^2)? I know the Nitsche lift "drop a derivative and add an h" trick doesn't always work even for scalar-valued elements ... thanks to stumbling upon an exception (Powell-Sabin-Heindl in H2) in grad school and then wasting weeks looking for a software bug where no software bug existed ... but I don't recall the rules for H(div) or H(curl) elements.

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 11, 2023

The TET14 case in the new vector_fe_ex6 is IMHO too slow in dbg mode: 2 minutes for me to run in serial. It's fine in devel (20s) and opt (15s), but we do run CI (and make check by hand) with dbg often enough that I don't want anything too expensive to add up. This is mostly an artifact of how we do build_cube with tets, I assume - subdivision into 24 tets means we don't have to worry about asymmetric diagonal selection on quad faces, but it also means that a 3.4K elem hex mesh turns into 80K tets; with RT0 here we go from like 14K DoFs to 240K. grid_size=6 gets us down to 16K tet DoFs, which finishes in under 10s in dbg mode, a fraction of a second in opt, which I think is about right.

Yeah, I noticed the same, we can lower the grid size as you suggest.

It was also a bit annoying to play with that; rather than forcing some parameters to be loaded only from input file and others only from command line, why not just have the same GetPot object read the input file and then override on the command line?

Well, yeah... I must admit I was bit confused by the situation w/ the Nedelec examples, so I just went with the same approach. I didn't realise the GetPot object allowed overrides via the command line.

I've pushed some commits for those things to my fork of your branch, if you want to look at them.

I'll apply your two commits at the tip of my branch and push in a sec. Thank you.

I'm happy with Paul's "check the convergence rates" test plan for now. Results:

L2 errors

elem grid=2 grid=4 grid=8 grid=16 grid=32
Tri 1.030 0.518 0.262 0.132 0.066
Quad 1.062 0.525 0.268 0.136 0.069
Hex 1.697 0.888 0.454 0.230 0.116
Tet 1.000 0.509 0.255 0.128 0.064
HDiv semi-norm errors

elem grid=2 grid=4 grid=8 grid=16 grid=32
Tri 2.411 1.343 0.741 0.424 0.257
Quad 2.890 1.622 0.873 0.479 0.276
Hex 5.071 2.862 1.524 0.815 0.451
Tet 2.506 1.347 0.736 0.420 0.253
The H(div) errors look roughly like what I'd have expected, but not quite. Shouldn't we be leveling off closer to C*h by the time we have h=1/32? 50% of the error after each subdivision, not 55%? And that 55-ish% seems pretty consistent through the table, there's not some obvious higher-order terms that are disappearing.

Ah! You were quicker, I was arriving at exactly the same conclusions... This is for TRI6, the black circles are C*h, which is what I'd expect for both L2 and H(div).... But you seem to have shown the same deviation happens across all element types :/
TRI6_convergence

I'm afraid I'm ignorant of what I should expect in L2. Do we only expect O(h) here, not O(h^2)? I know the Nitsche lift "drop a derivative and add an h" trick doesn't always work even for scalar-valued elements ... thanks to stumbling upon an exception (Powell-Sabin-Heindl in H2) in grad school and then wasting weeks looking for a software bug where no software bug existed ... but I don't recall the rules for H(div) or H(curl) elements.

According to this it should be O(h) for both (scroll to the pure Neumann on the scalar field, which equates to pure Dirichlet on the vector field, which is what I used).

@roystgnr
Copy link
Copy Markdown
Member

The natural thing to check first is always "where are we trying to cheat" and then "are we getting away with it" ... with that in mind, let's try modifying penalty ...

With triangles, grid_size=32, after making it configurable penalty=1e10 still gives me H(div) seminorm error of 0.257 ... and wow, are we ever insensitive to that! I have to go up to 1e15 or down to 1e2 before that seminorm error finally bumps up to 0.258. It blows up gradually if I go even bigger, or blows up quickly if I go even smaller (penalty=mild_suggestion), but this was clearly a red herring.

@roystgnr
Copy link
Copy Markdown
Member

after making it configurable

I pushed this to my branch, but I'd say don't bother cherry-picking it unless we find something actually useful to push alongside.

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 11, 2023

TRI6_convergence

It works but I don't particularly like it... I'm now solving a problem defined only up to a constant... The solver somehow finds the solution we seek but I'm a bit uncomfortable... Maybe I should use the penalty method to fix one of the scalar field's dofs....

@roystgnr
Copy link
Copy Markdown
Member

We were misconstraining p? I admit that boundary term wasn't what I'd have chosen, and in hindsight "evaluate a discontinuous variable at element boundaries" should have been an obvious code smell ... but surely we're now underconstrained instead? Is this example now going to scream and die the first time someone tries LU? (or even ILU? I often leave LIBMESH_OPTIONS set with cranked-up options there...)

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 11, 2023

We were misconstraining p? I admit that boundary term wasn't what I'd have chosen, and in hindsight "evaluate a discontinuous variable at element boundaries" should have been an obvious code smell ...

Yeah... I thought I was being smart by doing that...

but surely we're now underconstrained instead?

Indeed.

Is this example now going to scream and die the first time someone tries LU?

Yes...

@roystgnr
Copy link
Copy Markdown
Member

I pushed a fix for the unused variables issues to my fork. And I've just noticed that LIBMESH_OPTIONS='-pc_type lu' doesn't do anything because we're ignoring that variable...

@roystgnr
Copy link
Copy Markdown
Member

I guess it wouldn't kill us to not be compatible with LU in a simple example; we're already hard-coding -pc_type jacobi in vector_fe_ex3.

@roystgnr
Copy link
Copy Markdown
Member

We should probably put some comment about the problem in the example code, though.

@nmnobre nmnobre force-pushed the rt branch 3 times, most recently from 5e71742 to 8cf1525 Compare September 11, 2023 23:30
@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 11, 2023

Looking better now. To fix it, I'm instead adding the constraint that the integral over the volume (not the surface) must match that of the known solution. :) I've updated the html docs to match. The plot for the tets is shorter just because I didn't want to wait, no other reason. It'd be nice if you could independently regenerate the table above. :)

RT0_convergence

@jwpeterson
Copy link
Copy Markdown
Member

In case the exact solution isn't known, would you then just constrain the scalar variable average value to be 0? This is sometimes done for the pressure in the case of enclosed incompressible flows.

@karthichockalingam
Copy link
Copy Markdown
Contributor

karthichockalingam commented Sep 12, 2023

In case the exact solution isn't known, would you then just constrain the scalar variable average value to be 0? This is sometimes done for the pressure in the case of enclosed incompressible flows.

It is a problem if the exact solution is not known. I tried setting the average value to zero using the scalar Lagrange constraint in MOOSE. I don't believe it did anything to fix the issue.

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 12, 2023

In case the exact solution isn't known, would you then just constrain the scalar variable average value to be 0? This is sometimes done for the pressure in the case of enclosed incompressible flows.

This is precisely the net effect of the constraint I'm putting in, as the scalar field integrates to zero over the domain.
Is there an easier/cleaner way to enforce this in libMesh?

@dknez
Copy link
Copy Markdown
Member

dknez commented Sep 12, 2023

Regarding "setting the average value to zero": you can use a SCALAR variable for that kind of integral constraint, e.g. see systems_of_equations_ex5 for a similar case. (I haven't followed the details of this discussion so far, so my apologies if you've already looked into that.)

Another example is systems_of_equations_ex3. In that case we set the average value of the pressure, so it sounds similar to what you're discussing above.

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 12, 2023

Regarding "setting the average value to zero": you can use a SCALAR variable for that kind of integral constraint, e.g. see systems_of_equations_ex5 for a similar case. (I haven't followed the details of this discussion so far, so my apologies if you've already looked into that.)

Thanks @dknez, I hadn't looked into that, no.
I see it uses a scalar Lagrange multiplier, I could do the same here and put the average value of the scalar field on the rhs (to keep generality beyond cases where it averages to zero). I'll wait for @roystgnr and change it if he deems it necessary since the current formulation achieves the same. :)

I think I spotted a couple of typos in that example, when repositioning the submatrices (I didn't know you could do that either, that's something I could've used here too!) between lines 241 and 249, whenever you multiply, it should be n_u_dofs, not n_v_dofs, since you're jumping over the upper-left block of the matrix. In that case, it's purely cosmetic though because n_u_dofs = n_v_dofs.

@roystgnr
Copy link
Copy Markdown
Member

I'll wait for @roystgnr and change it if he deems it necessary since the current formulation achieves the same. :)

Nah; I'm happy enough as-is. I'm going to need to think harder about the problem before writing a RT-based mixed method myself, but this certainly looks adequate for demonstrating and testing the RT elements. Back in the day I would always just constrain away pressure kernels by adding a constraint pinning a single arbitrary point. :-D

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 12, 2023

Nah; I'm happy enough as-is.

While I was waiting for you I actually implemented the example using the Lagrange multiplier. 😅
I won't push if you don't want me to, but yeah... 😇

@roystgnr
Copy link
Copy Markdown
Member

Either way is fine; whichever you prefer.

I'm going to need to think harder about the problem before writing a RT-based mixed method myself

Or I need to just think harder about it because it's bugging the hell out of me. I can understand how trying to impose a Dirichlet BC on the boundaries of the discontinuous p variable could hurt the L2 convergence (constant, not rate) of p, adding a constant error term to it, because that's not the right way to get a high-order reconstruction there ... but I don't actually understand how that was hurting the H(div) convergence of u. We're solving u=-∇p, right? And ∇(constant) is still 0?

None of this should block merging, my ignorance here is a me problem not a fe_raviart.C problem, but it's like an itch...

@jwpeterson
Copy link
Copy Markdown
Member

While I was waiting for you I actually implemented the example using the Lagrange multiplier. 😅

I'd be in favor of having the Lagrange multiplier approach in the example (especially if you already coded it), since it seems like something that would be needed by anyone trying to extend the example.

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 12, 2023

We're solving u=-∇p, right?

Right.

And ∇(constant) is still 0?

Right.

None of this should block merging, my ignorance here is a me problem not a fe_raviart.C problem, but it's like an itch...

I think it's simply because I was doing this in a non-standard way. I'll try to explain my understanding.
The lower-left block the matrix, the one which imposes ∇·u = f, is not totally linearly independent, its rank is the number of its rows (number of scalar dofs) - 1. So I was adding to each boundary row a second term that said, it'd be nice if the integral along the surface matched my exact solution's integral along the surface. The sum of all these rows would then equate to a new equation that set the total integral along the surface to the integral along the surface of the exact solution (because the terms on the divergence sum to zero, recall they aren't linearly independent). The problem is, for each of the boundary rows, I was pushing the scalar dof to match the value of the function along the surface, when otherwise the system would tend to push it to the value of the function on the volume (as evidenced by the second shot I had at the method yesternight and today w/ the Lagrange multiplier). So ∇·u = f had to give in to accommodate for my incorrect pushing of the scalar dof in the same equations. Does that make sense or did I make it worse?

@roystgnr
Copy link
Copy Markdown
Member

The sum of all these rows would then equate to a new equation

But the catch is that you're not just adding that one new equation, you're really adding one equation per boundary scalar DoF? And so then the solver doesn't just cancel the one-dimensional kernel, it's being forced to tweak every boundary value a bit? The error in p then isn't just a constant, it's actually got a (weak) gradient, and that leaks into u. That would explain it, if I've got it right.

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 12, 2023

The sum of all these rows would then equate to a new equation

But the catch is that you're not just adding that one new equation, you're really adding one equation per boundary scalar DoF?

Yeah, that's it. It's not exactly one equation per boundary scalar dof, but I'm amending the existing equations with an extra term.

And so then the solver doesn't just cancel the one-dimensional kernel, it's being forced to tweak every boundary value a bit?

Exactly.

(I think I've learnt my lesson, Lagrange multipliers it is from now on 😥)

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 12, 2023

Lastly, there's something else I wanted to check with you.
Imagine we have a HEX27 mesh, is it possible - under the most general circumstances you can think of - that it's quirky enough that face 0, 1, 2 of one element doesn't match face 5, 3, 4 respectively of a neighbour? Or even if they did, could they be rotated in relation to each other? In other words, is any mesh a continuous deformation of a uniform arrangement of reference HEX27s?

@roystgnr
Copy link
Copy Markdown
Member

roystgnr commented Sep 12, 2023

In other words, is any mesh a continuous deformation of a uniform arrangement of reference HEX27s?

Nope. All you need is 5 hexes sharing an edge, and there's no way to get that by deforming any subset of a cartesian Hex mesh. If you need the deformation to also have a continuous inverse then the situation is even worse; just look at our build_sphere for a simple example, where we have 20 lines of valence-3 edges.

And there's no way to set up a perfectly consistent single orientation rule in general, even if you can handle the high and low valence edge cases somehow. Imagine starting with a Nx1x1 row of hexes, giving it a 90 degree twist from one end to the other, then transforming from x,y,z to theta,r,z and stitching where the last hex meets the first to make it a twisted torus. There's no set of orientations you can give those hexes without there being some point at which the orientation has to locally twist.

I was just discussing this on another ticket the other day - it's easy to split a hex into 6 tets, but the hex-to-tet code in libMesh splits them into 24 tets, because that way every face is symmetric and we don't have to worry about orientation, whereas the otherwise-more-reasonable 6-tet splits have to worry about whether there's any twists in the mesh that will lead to selection of two different diagonal edges from different sides of the same face.

@lindsayad
Copy link
Copy Markdown
Member

I don't know whether you want to do anything to modify the preconditioning. With the global saddle point, domain decomposition is not great; bjacobi/asm take > 400 linear iterations to converge. Interestingly mumps does not converge even with some shift on the diagonal. superLU and strumpack work however. The defaults do still work (albeit slowly they do converge) for the problem size, so I think what you have is fine

@roystgnr
Copy link
Copy Markdown
Member

roystgnr commented Sep 12, 2023

I don't know whether you want to do anything to modify the preconditioning. With the global saddle point, domain decomposition is not great; bjacobi/asm take > 400 linear iterations to converge.

I'd say leave it be; it's fast enough for a small scale example.

I'd be curious as to what you'd recommend, though, for cases like this in general. On 16 cores, with 3375 hexes, I see 683 Krylov iterations with default block Jacobi + ILU0; -sub_pc_factor_levels 1 brings that down to 204, whereas ASM with one level of overlap actually gets much worse somehow and with 2 levels is still at 396 iterations. My default (for poorly converging problems) "Hulk smash!" preconditioning (ILU4, ASM4) setting gets down to 25 iterations, but with a longer runtime, unsurprisingly. Would it be worth setting up a field split just to get the lagrange multiplier in it's own tiny block?

@roystgnr
Copy link
Copy Markdown
Member

roystgnr commented Sep 12, 2023

I was just mentioning this PR to other coworkers and realized I skipped over some important bits:

We also noticed TET14s are missing from MOOSE but, at least at first sight, it doesn't look like that would be a massive amount of work?

It ought to be trivial. Add entries to a couple MooseEnum declarations, and for the most part any TET14 code path could be handled via "use the TET10 code path but then run the result through UnstructuredMesh::all_complete_order()."

Lastly, we do have plans (and funding) to do a bit more with libMesh and MOOSE beyond Raviart-Thomas. It'd be great if we could have a chat about these... it seems it'd be quite a lot of work, so we'd like to better understand the required effort, but also to get your opinion on how relevant these new features would be, how these align with your vision for the future of libMesh/MOOSE, etc....

You're in the UK, so 6 hours ahead of me in Austin? Early morning this Friday (anywhere 8-10am my time, so 2-4pm yours) would work for me for a telecon; otherwise I'll have more time most mornings next week.

@lindsayad
Copy link
Copy Markdown
Member

I've never done a Schur complement for a single global constraint. It would be interesting to try at some point. Since the off-diagonal blocks have the relation B = C^T probably common Schur complement preconditioning strategies would be applicable

@roystgnr roystgnr merged commit da8c922 into libMesh:devel Sep 12, 2023
@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 13, 2023

Nope. All you need is 5 hexes sharing an edge, and there's no way to get that by deforming any subset of a cartesian Hex mesh. If you need the deformation to also have a continuous inverse then the situation is even worse; just look at our build_sphere for a simple example, where we have 20 lines of valence-3 edges.

I think I can see this. Please correct me if I'm wrong, but you start by building what looks like the Schlegel diagram of a cube (2d) or a tesseract (3d) and, yes, for the latter, now seeing it as a pure 3d object, all 20 "internal" edges are valence 3, which I believe is what you are referring to?

And there's no way to set up a perfectly consistent single orientation rule in general, even if you can handle the high and low valence edge cases somehow. Imagine starting with a Nx1x1 row of hexes, giving it a 90 degree twist from one end to the other, then transforming from x,y,z to theta,r,z and stitching where the last hex meets the first to make it a twisted torus. There's no set of orientations you can give those hexes without there being some point at which the orientation has to locally twist.

I understand the construction of the twisted torus (the 90 degree rotation being along an axis parallel to the x axis). But I don't get the conclusion, can you please expand?

I was just discussing this on another ticket the other day - it's easy to split a hex into 6 tets, but the hex-to-tet code in libMesh splits them into 24 tets, because that way every face is symmetric and we don't have to worry about orientation, whereas the otherwise-more-reasonable 6-tet splits have to worry about whether there's any twists in the mesh that will lead to selection of two different diagonal edges from different sides of the same face.

This is easy to understand I think, it could be the case that the diagonal edge on the common face of two neighbouring hexes didn't match in the 6-tet split case, but that won't happen with the 24-tet split. Got it.

@roystgnr
Copy link
Copy Markdown
Member

Correct about the sphere.

For the torus, picture the eta-zeta axes on each cube (choosing xi to be the direction pointing from one cube to the next). For most of the cubes, eta and zeta on one cube will (on their shared face) match those on the neighbor, but you have to have one "twist" at some point, where e.g. eta on one cube is zeta on its neighbor and zeta on the cube is negative eta on its neighbor. Whether that matters for you depends on what you're doing, of course. Our C0 elements don't care; this new 6-tet split will scream and die.

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Sep 13, 2023

For the torus, picture the eta-zeta axes on each cube (choosing xi to be the direction pointing from one cube to the next). For most of the cubes, eta and zeta on one cube will (on their shared face) match those on the neighbor, but you have to have one "twist" at some point, where e.g. eta on one cube is zeta on its neighbor and zeta on the cube is negative eta on its neighbor.

I think I was complicating. I misread/misunderstood what you said as meaning each cube would be rotated in relation to its neighbours, whereas you meant a single 90 degree rotation as seen from the end faces... My bad. All good!

@lindsayad
Copy link
Copy Markdown
Member

BTW I see O(h^2) convergence for the L2 norm in 'p'. I believe this is superconvergence

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Oct 24, 2023

BTW I see O(h^2) convergence for the L2 norm in 'p'. I believe this is superconvergence

Is this for the current mixed formulation or your new hybrid formulation?
If the former, does that happen for both BCs?

@lindsayad
Copy link
Copy Markdown
Member

The former. Yes, seeing it for both BC types

@nmnobre
Copy link
Copy Markdown
Member Author

nmnobre commented Oct 24, 2023

Interesting, do you have a branch I can play with?

@lindsayad
Copy link
Copy Markdown
Member

lindsayad commented Oct 25, 2023

It turned out to be under-integration during the error calculation. Adding some extra integration order removed the superconvergence. I will be pushing some more commits to #3668 tomorrow that adds the calculation of L2 norms for 'p'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants