Skip to content

add a DG SW model exemple#1283

Open
yolhan83 wants to merge 22 commits intoFerrite-FEM:masterfrom
yolhan83:master
Open

add a DG SW model exemple#1283
yolhan83 wants to merge 22 commits intoFerrite-FEM:masterfrom
yolhan83:master

Conversation

@yolhan83
Copy link
Copy Markdown
Contributor

This PR adds a shallow water RKDG exemple to showcase how this can be done in Ferrite.
I won't go further on this description since everything is in the md, please suggest anykind of clarification.

@yolhan83
Copy link
Copy Markdown
Contributor Author

Manifest should be in the gitignore ?

@yolhan83
Copy link
Copy Markdown
Contributor Author

Ok for review, sorry some tutos didnt work on my pc couldn't test doc locally, only could preview. Seems to work now

@termi-official
Copy link
Copy Markdown
Member

Really awesome, thanks for the addition!

Manifest should be in the gitignore ?

We have it in the CI to have a fully reproducible state for the docs, as we rely on a lot of packages in the docs. I will review later.

@termi-official termi-official self-requested a review February 11, 2026 16:28
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.25%. Comparing base (f245839) to head (87cfa1e).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1283   +/-   ##
=======================================
  Coverage   94.25%   94.25%           
=======================================
  Files          40       40           
  Lines        6750     6750           
=======================================
  Hits         6362     6362           
  Misses        388      388           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Member

@termi-official termi-official left a comment

Choose a reason for hiding this comment

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

First feedback from my side.

[...] some tutos didnt work on my pc [...]

What failure do you get?

@yolhan83
Copy link
Copy Markdown
Contributor Author

First feedback from my side.

[...] some tutos didnt work on my pc [...]

What failure do you get?

I get :

┌ Error: failed to run `@example` block in docs\src\tutorials\stokes-flow.md:459-461```@example stokes-flow
│ main()
```
│   exception =
│ 
│    Stacktrace:
│      [1] error(s::String)
│        @ Base .\error.jl:44
│      [2] open
│        @ C:\Users\yolha\.julia\artifacts\649584fb27316a4395dbb47fde904c83626feae5\lib\gmsh.jl:113 [inlined]
│      [3] togrid(filename::String; domain::String)
│        @ FerriteGmsh C:\Users\yolha\.julia\packages\FerriteGmsh\MH6r7\src\FerriteGmsh.jl:185
│      [4] togrid
│        @ C:\Users\yolha\.julia\packages\FerriteGmsh\MH6r7\src\FerriteGmsh.jl:176 [inlined]
│      [5] (::Main.var"__atexample__named__stokes-flow".var"#setup_grid##0#setup_grid##1")(dir::String)
│        @ Main.var"__atexample__named__stokes-flow" .\stokes-flow.md:213
│      [6] mktempdir(fn::Main.var"__atexample__named__stokes-flow".var"#setup_grid##0#setup_grid##1", parent::String; prefix::String)
│        @ Base.Filesystem .\file.jl:936
│      [7] mktempdir (repeats 2 times)
│        @ .\file.jl:932 [inlined]
│      [8] setup_grid(h::Float64)
│        @ Main.var"__atexample__named__stokes-flow" .\stokes-flow.md:210
│      [9] main()
│        @ Main.var"__atexample__named__stokes-flow" .\stokes-flow.md:462
│     [10] top-level scope
│        @ stokes-flow.md:460
│     [11] eval(m::Module, e::Any)
│        @ Core .\boot.jl:489
│     [12] #61
│        @ C:\Users\yolha\.julia\packages\Documenter\HdXI4\src\expander_pipeline.jl:856 [inlined]
│     [13] cd(f::Documenter.var"#61#62"{Module, Expr}, dir::String)
│        @ Base.Filesystem .\file.jl:101
│     [14] (::Documenter.var"#59#60"{Documenter.Page, Module, Expr})()
│        @ Documenter C:\Users\yolha\.julia\packages\Documenter\HdXI4\src\expander_pipeline.jl:855
│     [15] (::IOCapture.var"#12#13"{Type{InterruptException}, Documenter.var"#59#60"{Documenter.Page, Module, Expr}, IOContext{Base.PipeEndpoint}, IOContext{Base.PipeEndpoint}, Base.TTY, Base.TTY})()
│        @ IOCapture C:\Users\yolha\.julia\packages\IOCapture\Y5rEA\src\IOCapture.jl:170
│     [16] with_logstate(f::IOCapture.var"#12#13"{Type{InterruptException}, Documenter.var"#59#60"{Documenter.Page, Module, Expr}, IOContext{Base.PipeEndpoint}, IOContext{Base.PipeEndpoint}, Base.TTY, Base.TTY}, logstate::Base.CoreLogging.LogState)  
│        @ Base.CoreLogging .\logging\logging.jl:542
│     [17] with_logger(f::Function, logger::Base.CoreLogging.ConsoleLogger)
│        @ Base.CoreLogging .\logging\logging.jl:653
│     [18] capture(f::Documenter.var"#59#60"{Documenter.Page, Module, Expr}; rethrow::Type, color::Bool, passthrough::Bool, capture_buffer::IOBuffer, io_context::Vector{Any})
│        @ IOCapture C:\Users\yolha\.julia\packages\IOCapture\Y5rEA\src\IOCapture.jl:167
│     [19] runner(::Type{Documenter.Expanders.ExampleBlocks}, node::MarkdownAST.Node{Nothing}, page::Documenter.Page, doc::Documenter.Document)
│        @ Documenter C:\Users\yolha\.julia\packages\Documenter\HdXI4\src\expander_pipeline.jl:854
└ @ Documenter C:\Users\yolha\.julia\packages\Documenter\HdXI4\src\utilities\utilities.jl:47

Comment on lines +393 to +416
Ui = @view U[idofs]
for qp in 1:getnquadpoints(iv)
n = getnormal(iv, qp; here = true)
dF = getdetJdV(iv, qp)
hL = function_value(iv, qp, Ui, dr_h, dr_h .+ ndpc; here = true)
qxL = function_value(iv, qp, Ui, dr_qx, dr_qx .+ ndpc; here = true)
qyL = function_value(iv, qp, Ui, dr_qy, dr_qy .+ ndpc; here = true)
hR = function_value(iv, qp, Ui, dr_h, dr_h .+ ndpc; here = false)
qxR = function_value(iv, qp, Ui, dr_qx, dr_qx .+ ndpc; here = false)
qyR = function_value(iv, qp, Ui, dr_qy, dr_qy .+ ndpc; here = false)
UL = Vec{3}((hL, qxL, qyL))
UR = Vec{3}((hR, qxR, qyR))
fhat = LF(UL, UR, n)
for i in 1:nb
ΦᵢL = shape_value(iv, qp, i; here = true)
ΦᵢR = shape_value(iv, qp, i + nb; here = false)
Ri[dr_h[i]] += (-ΦᵢL * fhat[1]) * dF
Ri[dr_qx[i]] += (-ΦᵢL * fhat[2]) * dF
Ri[dr_qy[i]] += (-ΦᵢL * fhat[3]) * dF
Ri[dr_h[i] + ndpc] += (ΦᵢR * fhat[1]) * dF
Ri[dr_qx[i] + ndpc] += (ΦᵢR * fhat[2]) * dF
Ri[dr_qy[i] + ndpc] += (ΦᵢR * fhat[3]) * dF
end
end
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@AbdAlazezAhmed can you take a look at this part here? Any chance that we miss some interface for this, so users do not need to manually index this stuff? Also, aren't we missing some 1/h term?

Copy link
Copy Markdown
Contributor Author

@yolhan83 yolhan83 Feb 12, 2026

Choose a reason for hiding this comment

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

I found that weird using it too especially, I thought it should be either

hL = function_value(iv, qp, Ui, dr_h; here=true) 
hR = function_value(iv, qp, Ui, dr_h; here=false)

or

hL = function_value(iv, qp, Ui, dr_h) 
hR = function_value(iv, qp, Ui, dr_h .+ ndpc)

but having to do both seems weird. Maybe differencing Ui_here and Ui_there since the beggening would be better ?

Comment on lines +445 to +447
Rb[dr_h[i]] -= (Φᵢ * fhat[1]) * dF
Rb[dr_qx[i]] -= (Φᵢ * fhat[2]) * dF
Rb[dr_qy[i]] -= (Φᵢ * fhat[3]) * dF
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Correct me if I am wrong, but don't we need some element size term here to enforce the Dirichlet condition weakly?

Copy link
Copy Markdown
Contributor Author

@yolhan83 yolhan83 Feb 12, 2026

Choose a reason for hiding this comment

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

I'm using a ghost element method for the week forcing at the boundary, so the code is exactly similar to the inner interface beside that we dont compute the dofs for the ghost element and we preomcute what we want or wish at the outer trace. Maybe you're thinking of a strong one with a forcing term of order 1/size but that can lead to non-physical solution in this framework. It is ok when DG is used on parabolic / eliptic pdes though.

Copy link
Copy Markdown
Member

@termi-official termi-official Feb 12, 2026

Choose a reason for hiding this comment

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

Interesting, thanks for sharing this. Do you know some paper analyzing this? I have not read the theory part at the top yet, bit if it is not explained there yet, could you add the reference there?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The clearest I know is this one :
The Runge–Kutta discontinuous Galerkin method for conservation laws V: multidimensional systems](https://www.sciencedirect.com/science/article/pii/S0021999198958922
by B Cockburn and CW Shu its quite old 1998 but its just too well explained.

Of course can't do that without citing a friend who did his phd at the same time as I did,
Poussel, Camille, Mehmet Ersoy, and Frederic Golay. "Wetting and drying treatments with mesh adaptation for shallow water equations using a Runge–Kutta discontinuous Galerkin method." International Journal for Numerical Methods in Fluids 97.5 (2025): 692-712.

won't cite myself since I'm more on the modelling side but I used this method for every papers I did (with a twist for advection-diffusion problem)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

also if you want the theoric part to be clearer I can do that but I thought I would go really fast to the code since the point stay Ferrite

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I always struggle with a good balance for how much theory should be stated in the docs. @AbdAlazezAhmed what would you put in the docs for this example?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We also might want to put this tutorial before the Navier-Stokes tutorial, because it shows OrdinaryDiffEq withouth the funky parts to strongly enforce the Dirichlet boundary condition and discontinuity handling.

@yolhan83
Copy link
Copy Markdown
Contributor Author

comment for myself : if docs pass, remove the vtk gen froom CI

Comment on lines +445 to +447
Rb[dr_h[i]] -= (Φᵢ * fhat[1]) * dF
Rb[dr_qx[i]] -= (Φᵢ * fhat[2]) * dF
Rb[dr_qy[i]] -= (Φᵢ * fhat[3]) * dF
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I always struggle with a good balance for how much theory should be stated in the docs. @AbdAlazezAhmed what would you put in the docs for this example?

Comment on lines +445 to +447
Rb[dr_h[i]] -= (Φᵢ * fhat[1]) * dF
Rb[dr_qx[i]] -= (Φᵢ * fhat[2]) * dF
Rb[dr_qy[i]] -= (Φᵢ * fhat[3]) * dF
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We also might want to put this tutorial before the Navier-Stokes tutorial, because it shows OrdinaryDiffEq withouth the funky parts to strongly enforce the Dirichlet boundary condition and discontinuity handling.

@yolhan83
Copy link
Copy Markdown
Contributor Author

yolhan83 commented Feb 12, 2026

I think its ok for review again, besides waiting for some answer on the amount of math.
little note too : the first lines aren't hide in preview yet this is how litterate should handle this ?

Copy link
Copy Markdown
Collaborator

@lijas lijas left a comment

Choose a reason for hiding this comment

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

Nice work! I dont know anything about the shallow water equations, but from reading the introduction it seems like the fields are grouped in to a vector U = [h, qx, qy] ? In that case, I think it would be better to treat this as a vector problem instead of three scalar equations. The code will become cleaner and follow the equations much closer. Below I will give suggestion on how to update parts of the code, but you have to update other parts of the code accordingly... This requires some refactoring but I think it can be worth it.

droptol!(M, 1.0e-12)
M = Diagonal(M)
# ### Volume integral on each element
function element_volume_integral!(R, cell, U, dr, param)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Element rotine becomes:

function element_volume_integral!(R, cell, U, param)
    Re = param.Re
    fill!(Re, 0.0)
    cv = param.cv
    nb = getnbasefunctions(cv)
    Ferrite.reinit!(cv, cell)
    dofs = celldofs(cell)
    Ue = @view U[dofs] # Note: For performance reasons, we don’t recommend using this outside the DG framework; here, the DOFs are guaranteed to be stored contiguously in memory.
    coords = getcoordinates(cell)
    for qp in 1:getnquadpoints(cv)
        dE = getdetJdV(cv, qp)
        Uval = function_value(cv, qp, Ue)
        F = F_function(Uval)
        X = spatial_coordinate(cv, qp, coords)
        SS = S(Uval, X)
        for i in 1:nb
            Φᵢ = shape_value(cv, qp, i)
            ∇Φᵢ = shape_gradient(cv, qp, i)
            Re[i] += (F ⊡ ∇Φᵢ+ SS ⋅ Φᵢ) * dE
        end
    end
    Ferrite.assemble!(R, dofs, Re)
    return nothing
end

with


# Define double contraction operator for StaticArrays matrices
⊡(A::StaticMatrix, B::StaticMatrix) = sum(A .* B)


#-
# ### Mass matrix (block-diagonal per element)
function assemble_mass_matrix!(M, dh, cv)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Mass matrix routine simplifies to:

function assemble_mass_matrix!(M, dh, cv)
    nb = getnbasefunctions(cv)
    Me = zeros(nb, nb)

    asM = start_assemble(M)
    for cell in CellIterator(dh)
        Ferrite.reinit!(cv, cell)
        dofs = celldofs(cell)
        fill!(Me, 0.0)

        for qp in 1:getnquadpoints(cv)
            dE = getdetJdV(cv, qp)
            for i in 1:nb
                Φᵢ = shape_value(cv, qp, i)
                for j in 1:nb
                    Φⱼ = shape_value(cv, qp, j)
                    m = (Φᵢ ⋅ Φⱼ) * dE
                    Me[i, j] += m
                end
            end
        end

        assemble!(asM, dofs, inv(Me))
    end
    return M
end


# ### Interface integral on each interface
function interface_integral!(R, ic, U, dr, param)
dr_h, dr_qx, dr_qy = dr
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

function interface_integral!(R, ic, U, param)
    Ri = param.Ri
    fill!(Ri, 0.0)
    iv = param.iv
    nb = getnbasefunctions(param.cv)
    Ferrite.reinit!(iv, ic)
    idofs = interfacedofs(ic)
    Ui = @view U[idofs]
    for qp in 1:getnquadpoints(iv)
        n = getnormal(iv, qp; here = true)
        dF = getdetJdV(iv, qp)
        UL = function_value(iv, qp, Ui; here = true)
        UR = function_value(iv, qp, Ui; here = false)
        
        #Ujmp = function_value_jump(iv, qp, U)
        #c = max(max_speed(UL, n), max_speed(UR, n))
        #fhat = 0.5(F(UL)⋅n + F(UR)⋅n) - c*Ujmp
        
        fhat = LF(UL, UR, n)
        for i in 1:nb
            Φᵢ = 2*shape_value_jump(iv, qp, i)
            Ri[i] += (Φᵢ ⋅ fhat) * dF
        end
    end
    Ferrite.assemble!(R, idofs, Ri)
    return nothing
end

batpx(X) = -2 * (X[1] - 5) * 0.5 * exp(-(X[1] - 5)^2 - (X[2] - 5)^2)
batpy(X) = -2 * (X[2] - 5) * 0.5 * exp(-(X[1] - 5)^2 - (X[2] - 5)^2)

function F(U)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We need F to ouput and 3x2 matrix, which also matches your equations

function F(U)
    h, qx, qy = U
    ux = qx / h
    uy = qy / h
    p = g * h * h / 2
    return @SMatrix [qx       qy;
              qx*ux+p  qy*ux;
              qx*uy    qy*uy+p]
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Really nice thank you I will update afaik, I remember now why I didn't do that, I was a bit scared to mix SMatrix with Tensors but since you think it's all right 👍

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If we will merge Ferrite-FEM/Tensors.jl#236 in the future then mixing the SMatrix in here also won't be necessary anymore. :)

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.

4 participants