Skip to content

Add Navier–Stokes flow-around-cylinder example using projection method#630

Open
Ouardghi wants to merge 2 commits intoParallel-in-Time:masterfrom
Ouardghi:pr-stroemungsraum-nse
Open

Add Navier–Stokes flow-around-cylinder example using projection method#630
Ouardghi wants to merge 2 commits intoParallel-in-Time:masterfrom
Ouardghi:pr-stroemungsraum-nse

Conversation

@Ouardghi
Copy link
Contributor

This PR adds a new example for the incompressible Navier–Stokes equations using a projection method for the classical flow around a cylinder benchmark.

The goal of this contribution is to provide a working example of a projection-based Navier–Stokes solver within the pySDC framework.

This PR includes

  • A new problem class implementing the Navier–Stokes equations
  • A sweeper implementing the projection-based time-stepping method
  • A mesh file for the cylinder benchmark geometry
  • Tests to verify the functionality of the implementation
  • A small run script to execute the simulation

This example can serve as a reference for implementing incompressible flow solvers and for experimenting with projection-based approaches in pySDC.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new 2D incompressible Navier–Stokes (flow around a cylinder) example for the StroemungsRaum project using a projection (fractional-step) method in a FEniCS-based problem class, plus a custom IMEX mass-matrix sweeper, a run script, and tests.

Changes:

  • Introduce fenics_NSE_2D_mass problem class implementing the projection-based NSE solve and RHS splitting.
  • Add imex_1st_order_mass_NSE sweeper adapting the IMEX mass-matrix sweeper for NSE (including custom residual handling).
  • Add a runnable setup/run script and a FEniCS-marked pytest suite validating solve_system, eval_f, and a benchmark-based drag/lift check.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 8 comments.

File Description
pySDC/projects/StroemungsRaum/problem_classes/NavierStokes_2D_FEniCS.py New FEniCS NSE problem class with projection method, BCs, and drag/lift measure.
pySDC/projects/StroemungsRaum/sweepers/imex_1st_order_mass_NSE.py New IMEX mass sweeper variant calling the NSE projection solve and custom residual logic.
pySDC/projects/StroemungsRaum/run_Navier_Stokes_equations_FEniCS.py New setup + execution helper for running the cylinder benchmark.
pySDC/projects/StroemungsRaum/tests/test_Navier_Stokes_IMEX_FEniCS.py New tests for the NSE system solve, RHS evaluation, and benchmark coefficients.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +75 to +115
def compute_residual(self, stage=None):
"""
Computation of the residual using the collocation matrix Q

Args:
stage (str): The current stage of the step the level belongs to
"""

# get current level and problem description
L = self.level

# Check if we want to skip the residual computation to gain performance
# Keep in mind that skipping any residual computation is likely to give incorrect outputs of the residual!
if stage in self.params.skip_residual_computation:
L.status.residual = 0.0 if L.status.residual is None else L.status.residual
return None

# compute the residual for each node
# build QF(u)
res_norm = []
res = [0] * (self.coll.num_nodes + 1)
for m in range(self.coll.num_nodes):

# compute the residual at node m, using the incremental criterion
if L.uold[m + 1] is not None:
res[m] = L.u[m + 1] - L.uold[m + 1]
else:
res[m] = L.u[m + 1]

# Due to different boundary conditions we might have to fix the residual
if L.prob.fix_bc_for_residual:
L.prob.fix_residual(res[m])
# use abs function from data type here
res_norm.append(abs(res[m]))

# find maximal residual over the nodes
L.status.residual = max(res_norm)

if L.time == 3.1250000000e-04 and L.status.residual == 0.0:
L.status.residual = 1.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 don't like this magic number either..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right, I should use the initial time t0 instead of a hard-coded value.

I introduced this check because zero is a valid solution to the problem. Since the residual is evaluated first, it becomes zero, and the simulation terminates immediately. To prevent this, I modify the residual only at the first time step, and only if it is zero, so that the iteration can proceed:

if L.time == L.prob.t0 and L.status.residual == 0.0:
L.status.residual = 1.0

Copy link
Contributor

Choose a reason for hiding this comment

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

We fixed this issue here by always doing one SDC iteration, no? Is this really still needed?
In this file you can also see that you should pass level_params['e_tol'] = x to get an increment tolerance of x rather than assign the increment to the residual and use residual tolerance.

class fenics_NSE_2D_mass(Problem):
r"""
Example implementing a forced two-dimensional incompressible Navier–Stokes problem for the DFG
benchmark flow around cyilder using FEniCS (dolfin).
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

@brownbaerchen brownbaerchen left a comment

Choose a reason for hiding this comment

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

Thanks for the PR, much cleaner than previous ones! I didn't really check the maths, but I trust you with it.

"""
Custom sweeper class, implements Sweeper.py

First-order IMEX sweeper using implicit/explicit Euler as base integrator, with mass or weighting matrix
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
First-order IMEX sweeper using implicit/explicit Euler as base integrator, with mass or weighting matrix
First-order IMEX sweeper with mass or weighting matrix including a project step for Navier-Stokes equation

Not sure if this is the best possible docstring, but the preconditioner is a parameter and should not be mentioned here.

Comment on lines +195 to +196
u = self.dtype_u(u0)
p = df.Function(self.Q)
Copy link
Contributor

Choose a reason for hiding this comment

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

This problem could probably benefit from its own datatype that gathers u and p into a single datatype. It's not urgently needed and since this project is at its end, maybe don't do it? What do you think @pancetta?

Comment on lines +98 to +102
# compute the residual at node m, using the incremental criterion
if L.uold[m + 1] is not None:
res[m] = L.u[m + 1] - L.uold[m + 1]
else:
res[m] = L.u[m + 1]
Copy link
Contributor

Choose a reason for hiding this comment

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

What's going on here? If you want to use an incremental tolerance for stopping the iteration, that is done elsewhere. The residual should be the residual and nothing else.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is hard to use the residual here, as the residual corresponds only to the first step of the projection method, where SDC is applied. However, the solver actually consists of multiple coupled substeps: after computing the tentative velocity, a pressure correction is performed, and the velocity is projected to enforce incompressibility.

I thought the easiest way to handle this was to modify the residual. Is there a simpler or alternative way to achieve this?

Copy link
Contributor

Choose a reason for hiding this comment

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

As mentioned above, we have infrastructure for using increment tolerance, you can simply pass residual tolerance and no residual tolerance.

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.

5 participants