Navier-Stokes Equation

Hello, my goal is to solve the full Navier-Stokes equations for a beam immersed in a fluid (air). The inlet velocity profile is parabolic, with a ramping factor applied to ensure a smoother transition at the start of the simulation. However, after approximately 100 iterations, the solution stops converging. Could this be caused by how I’ve configured the time integration method or the boundary conditions?

import getfem as gf 
from regions_check import verify_regions
import numpy as np
import os

gf.util_trace_level(1)
gf.util_warning_level(1)
π = np.pi
################################
# Beam in a Fluid:
#################################
'''
The goal is to solve the full Navier-Stokes equations for a beam immeresed in a fluid.
'''

##################
## PROBLEM DATA ##
##################
"""
The fluid it will be the air
"""

scale_factor = 1 #m -> m  (it can be used if we want to pass from meter to anything else cm, dm etc...) 


# Fluid properties (air):
ν_fluid = 0.015e-4 * scale_factor**2                # m²/s 
rho_fluid = 1.23 /(scale_factor**3  )               # kg/m³ 



# Boundaries values: 
# Inlet velocity parameters
U_mean = 1.0     # Mean inlet velocity
H = 0.41           # Channel height

# Transient paramters: 
T = 10.0           # Total simulation time
dt = 1e-3         # Time step
theta = 0.5      # Theta parameter (0.5 = Crank-Nicolson)



#############
## MESH ##
#############

Mesh_fluid= gf.Mesh('Import', 'gmsh','MESH_GMSH/Fluid.msh')

#############
## REGIONS ##
#############
"""
regions flagging in gmsh does not work thus it's necessary to give a region for each line or physical surface. 
 
"""

Beam_left = 1 
Beam_bottom = 2 
Beam_right = 3 
Beam_top = 4 
INLET = 5 
Wall_bottom = 6
OUTLET = 7  
Wall_top = 8 

BEAM_INTERFACE = 100

Mesh_fluid.region_merge(BEAM_INTERFACE, Beam_left)
Mesh_fluid.region_merge(BEAM_INTERFACE, Beam_right)
Mesh_fluid.region_merge(BEAM_INTERFACE, Beam_bottom)
Mesh_fluid.region_merge(BEAM_INTERFACE, Beam_top)

# physical surface: 
fluid1 = 9
fluid2 = 11
fluid3 = 10
fluid4 = 13


FLUID = 101
Mesh_fluid.region_merge(FLUID,fluid1)
Mesh_fluid.region_merge(FLUID,fluid2)
Mesh_fluid.region_merge(FLUID,fluid3)
Mesh_fluid.region_merge(FLUID,fluid4)

WALLS  = 102
Mesh_fluid.region_merge(WALLS,Wall_top)
Mesh_fluid.region_merge(WALLS,Wall_bottom)

verify_regions(Mesh_fluid, 'meshfluid') #it cna be used verify regions againg to see if the regions are set up correctly.

#verify_regions(Mesh_solid, 'meshsolid')
########################
## INTEGRATION METHOD ##
########################
"""
The integration method is quadrature with 17 points, which is suitable for QK elements.  
"""
mim_fluid = gf.MeshIm(Mesh_fluid, gf.Integ('IM_QUAD(17)'))


#########################
## FEM ELEMENTS METHOD ##
########################
"""
THe meshes are quadrilater, hence QK elements are used. 
"""

## FEM ELEMENTS:

mfv_fluid_ = gf.MeshFem(Mesh_fluid, 2)
mfv_fluid_.set_fem(gf.Fem('FEM_QK(2,2)'))

mfp_fluid = gf.MeshFem(Mesh_fluid, 1)
mfp_fluid.set_fem(gf.Fem('FEM_QK(2,1)'))

######################
## REMOVE FIXED DOF ##
######################
"""
Instead of applying dirichelt boundary coniditons, the degree of freedom are removed directly here.
"""

kept_dofs_v_f = list(
                set(range(mfv_fluid_.nbdof()))
                -set(mfv_fluid_.basic_dof_on_region(WALLS))
                -set(mfv_fluid_.basic_dof_on_region(BEAM_INTERFACE))
                )
                

mfv_fluid = gf.MeshFem('partial', mfv_fluid_, kept_dofs_v_f)


###########
## MODEL ##
###########

md = gf.Model("real")


###################
## FEM VARIABLES ##
###################
"""
Fem variable are defined on the MeshFEM with removed degree of freedom. 

"""


md.add_fem_variable("v_f", mfv_fluid)       # Fluid velocity  
md.add_fem_variable("p", mfp_fluid)       # Pressure

# Previous time step data
md.add_fem_data("Previous_v_f", mfv_fluid)


###########################
## INITIALIZED CONSTANTS ##
###########################
"""
The material properties and the times variable are initiualized in the model 
"""
md.add_initialized_data("rho_f", rho_fluid)
md.add_initialized_data("nu_f", ν_fluid)
md.add_initialized_data("dt", dt)
md.add_initialized_data("theta", theta)
md.add_initialized_data("H", H)
md.add_initialized_data("U_mean", U_mean)
md.add_initialized_data("p_out", 0)
# Time variable
md.add_initialized_data("t", 0.0)
# Define inlet profile as a macro (parabolic profile)
md.add_macro("inlet_profile(y)", "1.5*U_mean*4*y*(H-y)/(H*H)")


########################
## INITIAL CONDITIONS ##
########################
"""
Initialize all to zero (at rest), since we're using filtered fem variable, it's not possibble to 
use md.interpolation. 
"""

# Initialize with zero vectors of the correct size
md.set_variable("v_f", np.zeros(mfv_fluid.nbdof()))
md.set_variable("p", np.zeros(mfp_fluid.nbdof()))

# Also set previous values
md.set_variable("Previous_v_f", np.zeros(mfv_fluid.nbdof()))

######################
## WEAK FORMULATION ##
# ######################
"""
The problem is formulated in weak form, and for the time the theta method is used.
"""


# FLUID : 
# stress tensors: 
md.add_macro('sigma_f_vu(v)', "2*rho_f*nu_f*(Grad_v + (Grad_v)) ")
md.add_macro('sigma_f_p(p)', '-p*Id(2)')

md.add_macro("Convection(v)", "(rho_f*v.Grad_v)")
md.add_macro('Incompressibility(v)', "Trace(Grad_v)")
md.add_macro("Stress_vu(v)", 'sigma_f_vu(v)')
md.add_macro("Stress_p(p)", "sigma_f_p(p)")

Transient_fluid = '(rho_f*theta*(v_f - Previous_v_f)). Test_v_f' \
            '+ (rho_f*(1-theta)*(v_f - Previous_v_f)). Test_v_f '

md.add_nonlinear_term(mim_fluid, Transient_fluid, FLUID)


md.add_linear_term(mim_fluid, ' dt*Incompressibility(v_f)*Test_p', FLUID) 

md.add_nonlinear_term(mim_fluid,"dt*theta*(Convection(v_f)).Test_v_f +" \
                                "dt*(1-theta)*(Convection(Previous_v_f)).Test_v_f", FLUID)

md.add_linear_term(mim_fluid,"dt*theta*Stress_vu(v_f):Grad_Test_v_f +" \
                                "dt*(1-theta)*(Stress_vu(Previous_v_f):Grad_Test_v_f)", FLUID)
md.add_linear_term(mim_fluid, 'dt*Stress_p(p):Grad_Test_v_f', FLUID)




#####################
# BOUNDARY CONDITIONS
# Inlet velocity profile (parabolic, time-dependent with smooth ramp)
ramp_factor = 0.0
V_inlet_expr = f"{ramp_factor}*[inlet_profile(X(2)), 0]"
V_inlet_full = md.interpolation(V_inlet_expr, mfv_fluid_)
V_inlet = V_inlet_full[kept_dofs_v_f]

md.add_initialized_fem_data('V_inlet', mfv_fluid, V_inlet) 

# Replace penalty terms with:
md.add_Dirichlet_condition_with_penalization(mim_fluid, "v_f", 10**3, INLET, "V_inlet")
md.add_Dirichlet_condition_with_penalization(mim_fluid, 'p', 10**3, OUTLET)

####################
## MODEL SOLOTION ##
####################

# Create output directory
output_dir = "Results"
os.makedirs(output_dir, exist_ok=True)

# TIME STEPPING LOOP

print(f"Time step: {dt}, Total time: {T}")
print(f"Theta: {theta} (Crank-Nicolson)" if theta == 0.5 else f"Theta: {theta}")

t = 0.0
step = 0
export_every = int(0.01/dt)  # Export every 0.01s



# Main time loop
while t < T:
    
    md.set_variable("t", t)
    if t < 2.0:
        ramp_factor = 0.5 * (1 - np.cos(np.pi * t / 2.0))
    else:
        ramp_factor = 1.0
    V_inlet_expr = f"{ramp_factor}*[inlet_profile(X(2)), 0]"
    V_inlet_full = md.interpolation(V_inlet_expr, mfv_fluid_)
    V_inlet = V_inlet_full[kept_dofs_v_f]
    md.set_variable("V_inlet", V_inlet)
    if step > 0:  # After first solve
        
        print(f'time is: {t} and ramp_factor is {ramp_factor} and velocity is {V_inlet}')
    
    
    # More robust solver parameters
    md.solve("very noisy", 
         "max_iter", 100,
         "max_res", 1e-6,  
         "lsolver", "superlu",
         "lsearch", "basic",  
         "alpha min", 1e-4,  
         "alpha mult", 0.5)    

    # Extract current solution

    v_f = md.variable("v_f")
    p = md.variable("p")
   
    

    mfv_fluid.export_to_vtu(f"{output_dir}/fluid_{step:05d}.vtu",
                            mfv_fluid, v_f, "Velocity", 
                            mfp_fluid, p, "Pressure")
    
    
    md.set_variable("Previous_v_f", v_f)
    
    
    # Advance time
    t += dt
    step += 1
    

Hello, nice project, something weird that I have spotted is this: IM_QUAD(17)

A 17th order integration looks a bit extreme.

Otherwise I lack the mesh file for running your code.

Hello,

thank you for the answer.

I know that the integration order is high I was trying to see if something would change if I increased it and I forgot it to change it back, but it doesn’t seem to affect the solution.

I’m not sure how to share the .msh file with you, but this is the .geo file. Could I also ask you which is, in your opinion, the best way to impose boundary conditions and which solver to set in case of a non-linear problem?"


////////////////////////////////////////////////////////////
// Constants
h_ref = 0.5;
L = 10;
thickness = 0.005*L;

// Number of elements along each direction
nx1 = 20;  // Left of beam
nx2 = 40;  // Along beam
nx3 = 20;  // Right of beam
ny1 = 20;  // Below/Above beam (symmetric)
ny2 = 5;   // Along beam thickness

// Fluid domain dimensions (made symmetric)
domain_x_min = -2*L;     
domain_x_max = 3*L;      
domain_y_min = -2*L;       // Made symmetric with domain_y_max
domain_y_max = 2*L;      

////////////////////////////////////////////////////////////
// Points Definition
////////////////////////////////////////////////////////////
// Outer corners
Point(1) = {domain_x_min, domain_y_min, 0, h_ref};  // Bottom left
Point(2) = {domain_x_max, domain_y_min, 0, h_ref};  // Bottom right
Point(3) = {domain_x_max, domain_y_max, 0, h_ref};  // Top right
Point(4) = {domain_x_min, domain_y_max, 0, h_ref};  // Top left

// Points for beam
Point(5) = {0, -thickness/2, 0, h_ref};                        // Beam bottom left
Point(6) = {L, -thickness/2, 0, h_ref};                        // Beam bottom right
Point(7) = {L, thickness/2, 0, h_ref};                // Beam top right
Point(8) = {0, thickness/2, 0, h_ref};                // Beam top left



//+
Line(1) = {8, 5};
//+
Line(2) = {5, 6};
//+
Line(3) = {6, 7};
//+
Line(4) = {7, 8};

//+
Line(5) = {4, 1};
//+
Line(6) = {1, 2};
//+
Line(7) = {2, 3};
//+
Line(8) = {3, 4};
//+

// Meshing lines
//+
Line(9) = {8, 4};
//+
Line(10) = {5, 1};
//+
Line(11) = {7, 3};
//+
Line(12) = {6, 2};

Curve Loop(1) = {5, 6, 7, 8};
//+
Curve Loop(2) = {2, 3, 4, 1};


//+
Physical Curve("beam_left", 9) = {1};
//+
Physical Curve("beam_bottom", 10) = {2};
//+
Physical Curve("beam_right", 11) = {3};
//+
Physical Curve("beam_top", 12) = {4};
//+
Physical Curve("Inlet", 14) = {5};
//+
Physical Curve("Outlet", 15) = {7};
//+
Physical Curve("Walls", 16) = {8, 6};
//+

//+
Curve Loop(3) = {5, -10, -1, 9};
//+
Plane Surface(3) = {3};
//+
Curve Loop(4) = {4, 9, -8, -11};
//+
Plane Surface(4) = {4};
//+
Curve Loop(5) = {2, 12, -6, -10};
//+
Plane Surface(5) = {5};
//+
Curve Loop(6) = {12, 7, -11, -3};
//+
Plane Surface(6) = {6};
//+

// Physical Surfaces
Physical Surface("fluid", 18) = {3, 4, 5, 6};

//+
Transfinite Surface {3} = {1, 5, 8, 4};
//+
Transfinite Surface {5} = {1, 2, 6, 5};
//+
Transfinite Surface {6} = {6, 2, 3, 7};
//+
Transfinite Surface {4} = {3, 4, 8, 7};
//+
Transfinite Surface {2} = {8, 5, 6, 7};

//+
N_v = 20;
N_o = 60;
N= 80;
p = 1.05;
Transfinite Curve {5, 1} = N_v Using Progression 1;
//+
Transfinite Curve {9, 10} = N Using Progression p;


//+
Transfinite Curve {6, 2} = N_o Using Progression 1;
//+
Transfinite Curve {10, 12} = N Using Progression p;


//+
Transfinite Curve {8, 4} = N_o Using Progression 1;
//+
Transfinite Curve {11, 9} = N Using Progression p;


//+
Transfinite Curve {7, 3} = N_v Using Progression 1;
//+
Transfinite Curve {12, 11} = N Using Progression p;


Recombine Surface{3,4,5,6}; 
Mesh.Algorithm = 6;

Mesh 2;
Save "Fluid.msh";

already in the iteration that converge you see a checkerboard pattern in the velocity field

the discretization of your v-p fields does not seem to be stable

I have also noticed this

(Grad_v + (Grad_v))

do you mean

(Grad(v) + Grad(v)')

?

One can also us the sym operator

2*Sym(Grad(v))

In md.solve, do not use “very noisy“ it is just annoying. Just use “noisy“. I also prefer to use mumps instead of superlu. If your finite element spaces and equations are ok, then you will not really need linesearch. In general, linesearch for mixed problems is ugly. Unless I model contact, or some other semismooth problem, I prefer to write a standard undamped Newton loop.

This here I do not understand either

theta will cancel out in the sum of these two subexpressions

Hello, Thanks for the advice! I’ve fixed the errors and modified the problem to simulate a cylinder immersed in fluid at a lower Reynolds number. However, I’m running into some issues: 1. The solution appears to be laminar with no vortex shedding occurring 2. The velocity field should be divergence-free, but I’m consistently getting divergence values around 0.05. Here’s my current implementation:

import getfem as gf 
import numpy as np
import os



############################{####
# Cylinder in a Fluid:
#################################
'''
The goal is to solve the full Navier-Stokes equations for a cylinder immeresed in a fluid.
'''

##################
## PROBLEM DATA ##
##################

# Fluid properties (artificial values for a Reynolds number of 60 ):
ν_fluid = 1/60 
rho_fluid = 1.204               # kg/m³ 
mu_fluid = rho_fluid * ν_fluid          # Dynamic viscosity kg/(m·s)


# Boundaries values: 
# Inlet velocity parameters
U_mean = 1      # Mean inlet velocity m/s

# Reference values for coefficients
D = 1  # Cylinder diameter (you need to set this based on your mesh)
A_ref = D   # Reference area (diameter × unit depth for 2D)
q_inf = 0.5 * rho_fluid * U_mean**2  # Dynamic pressure


# Transient paramters: 
T = 5.0           # Total simulation time
dt = 0.002       # Time step
theta = 0.5      # Theta parameter (0.5 = Crank-Nicolson)


print(f"Reynolds number is: {U_mean*D/ν_fluid} and number of time steps: {int(T/dt)}")
print(f"Strouhal number is approximately: {0.2}, thus a peiod is T = {1/0.2*D/U_mean}")

############
## MESH ##
#############

Mesh_fluid= gf.Mesh('Import', 'gmsh','Mesh/cylinder.msh')
h = min(Mesh_fluid.convex_radius())
print( f"Minimum mesh size h ={h}, and CFL = "f"{1}, thus dt = {dt} should be less than {1*h/U_mean}" )

#############
## REGIONS ##
#############
"""
regions flagging in gmsh does not work thus it's necessary to give a region for each line or physical surface. 

"""

Bottom_left = 1 
Bottom_right = 2
Top_left = 3
Top_right = 4 
INLET = 5 
OUTLET = 7  
Cylinder_1 = 8
Cylinder_2 = 9
Cylinder_3 = 10
Cylinder_4 = 11
print("Regions in the mesh are:", Mesh_fluid.regions())

CYLINDER_INTERFACE = 2001

Mesh_fluid.region_merge(CYLINDER_INTERFACE, Cylinder_1)
Mesh_fluid.region_merge(CYLINDER_INTERFACE, Cylinder_2)
Mesh_fluid.region_merge(CYLINDER_INTERFACE, Cylinder_3)
Mesh_fluid.region_merge(CYLINDER_INTERFACE, Cylinder_4)

# physical surface: 
fluid1 = 6 
fluid2 = 12
fluid3 = 13
fluid4 = 14
fluid5 = 15

FLUID = 2002
Mesh_fluid.region_merge(FLUID,fluid1)
Mesh_fluid.region_merge(FLUID,fluid2)
Mesh_fluid.region_merge(FLUID,fluid3)
Mesh_fluid.region_merge(FLUID,fluid4)
Mesh_fluid.region_merge(FLUID,fluid5)

WALLS  = 2003
Mesh_fluid.region_merge(WALLS,Bottom_left)
Mesh_fluid.region_merge(WALLS,Bottom_right)
Mesh_fluid.region_merge(WALLS,Top_left)
Mesh_fluid.region_merge(WALLS,Top_right)

########################
## INTEGRATION METHOD ##
########################
"""
The integration method is quadrature with 5 points, which is suitable for QK elements.  
"""
mim_fluid = gf.MeshIm(Mesh_fluid, gf.Integ('IM_QUAD(5)'))


#########################
## FEM ELEMENTS METHOD ##
########################1
"""
THe meshes are quadrilater, hence QK elements are used. 
"""

## FEM ELEMENTS:

mfv_fluid = gf.MeshFem(Mesh_fluid, 2)
mfv_fluid.set_fem(gf.Fem('FEM_QK(2,2)'))

mfp_fluid = gf.MeshFem(Mesh_fluid, 1)
mfp_fluid.set_fem(gf.Fem('FEM_QK(2,1)'))




###########
## MODEL ##
###########

md = gf.Model("real")


###################
## FEM VARIABLES ##
###################
"""
Fem variable are defined on the MeshFEM with removed degree of freedom. 

"""


md.add_fem_variable("v_f", mfv_fluid)        # Fluid velocity  
md.add_fem_variable("p", mfp_fluid)          # Pressure

# Previous time step data
md.add_fem_data("Previous_v_f", mfv_fluid)


###########################
## INITIALIZED CONSTANTS ##
###########################
"""
The material properties and the times variable are initiualized in the model 
"""
md.add_initialized_data("rho_f", rho_fluid)
md.add_initialized_data("nu_f", ν_fluid)
md.add_initialized_data("dt", dt)
md.add_initialized_data("theta", theta)
md.add_initialized_data("U_mean", U_mean)

# Time variable
md.add_initialized_data("t", 0.0)
# Define inlet profile as a macro (parabolic profile)


########################
## INITIAL CONDITIONS ##
########################
"""
Initialize all to zero (at rest), since we're using filtered fem variable, it's not possibble to 
use md.interpolation. 
"""

# Initialize with zero vectors of the correct size
md.set_variable("v_f", np.zeros(mfv_fluid.nbdof()))
md.set_variable("p", np.zeros(mfp_fluid.nbdof()))

# Also set previous values
md.set_variable("Previous_v_f", np.zeros(mfv_fluid.nbdof()))

######################

## WEAK FORMULATION ##
# ######################
"""
The problem is formulated in weak form and for the time the theta method is used.
"""


# FLUID : 
# stress tensors: 
md.add_macro('Stress_vu(v)', "rho_f*nu_f*2*Sym(Grad_v) ")
md.add_macro('Stress_p(p)', '-p*Id(2)')
md.add_macro("Convection(v)", "(rho_f*v.Grad_v)")
md.add_macro('Incompressibility(v)', "Trace(Grad_v)")


Transient_fluid = '(rho_f/dt)*(v_f - Previous_v_f). Test_v_f' 
            
md.add_nonlinear_term(mim_fluid, Transient_fluid, FLUID)


md.add_linear_term(mim_fluid, ' Incompressibility(v_f)*Test_p', FLUID) 

md.add_nonlinear_term(mim_fluid,"theta*(Convection(v_f)).Test_v_f +" \
 "(1-theta)*(Convection(Previous_v_f)).Test_v_f", FLUID)

md.add_linear_term(mim_fluid,"theta*Stress_vu(v_f):Grad_Test_v_f +" \
                                "(1-theta)*(Stress_vu(Previous_v_f):Grad_Test_v_f)", FLUID)
md.add_linear_term(mim_fluid, 'Stress_p(p):Grad_Test_v_f', FLUID)


#####################
# BOUNDARY CONDITIONS
#####################


V_inlet = md.interpolation("[U_mean, 0]", mfv_fluid)
md.add_initialized_fem_data('V_inlet', mfv_fluid, V_inlet) 

V_walls = md.interpolation("[U_mean, 0]", mfv_fluid)
md.add_initialized_fem_data('V_walls', mfv_fluid, V_walls) 

V_cylinder = md.interpolation("[0, 0]", mfv_fluid)
md.add_initialized_fem_data('V_cylinder', mfv_fluid, V_cylinder) 


# Apply Dirichlet conditions with multipliers
md.add_Dirichlet_condition_with_multipliers(mim_fluid, "v_f", mfv_fluid, INLET, "V_inlet")
md.add_Dirichlet_condition_with_multipliers(mim_fluid, "v_f", mfv_fluid, WALLS, "V_inlet")
md.add_Dirichlet_condition_with_multipliers(mim_fluid, "v_f", mfv_fluid, CYLINDER_INTERFACE, "V_cylinder")



####################
## MODEL SOLOTION ##
####################

# Create output directory
output_dir = "Results"
os.makedirs(output_dir, exist_ok=True)

# TIME STEPPING LOOP

print(f"Time step: {dt}, Total time: {T}, number of steps: {int(T/dt)}")

t = 0.0
step = 0


time_history = []
cd_history = []
cl_history = []


# Main time loop
while t < T:
    
  md.set_variable("t", t)
  md.solve("noisy", 
         "max_iter", 100,
         "max_res", 1e-8,  
         "lsolver", "superlu",  
         "alpha min", 1e-4,  
         "alpha mult", 0.5)    

  # Extract current solution
  v_f = md.variable("v_f")
  p = md.variable("p")

  print(f'time is: {t} the velocity is {v_f}')

  
  time_ms = int(t * 1000)  # Convert to milliseconds
  mfv_fluid.export_to_vtu(f"{output_dir}/fluid_{time_ms:06d}.vtu",
                        mfv_fluid, v_f, "Velocity", 
                        mfp_fluid, p, "Pressure")
    


  md.set_variable("Previous_v_f", v_f)

  ###### CL and CD computation ######
  traction = gf.asm_generic(mim_fluid, 0, "Stress_vu(v_f)*Normal+ Stress_p(p)*Normal", CYLINDER_INTERFACE, md)
  Fx, Fy = -traction[0], -traction[1]
  Cd = Fx / (q_inf * A_ref)
  Cl = Fy / (q_inf * A_ref)
  print(f"Time: {t:.4f}, Cd: {Cd:.6f}, Cl: {Cl:.6f}")

  time_history.append(t)
  cd_history.append(Cd)
  cl_history.append(Fy)
  np.savetxt(f"force_coefficients.txt", 
        np.column_stack([time_history, cd_history, cl_history]),
        header="Time, Cd, Cl")
  
  ## some checks to see if the solution is resonable ##
  div_norm = np.sqrt(gf.asm_generic(mim_fluid, 0, 'pow((Trace(Grad_v_f)),2)', FLUID, md))
  print("‖div(v_f)‖ₗ₂ =", div_norm)

  # Advance time
  t += dt
  step += 1
  
print("simulation completed.")

and here there is the .geo file for the mesh:

// Cylinder Mesh

Nx1 = 61; Rx1 = 1.00;
Nx2 = 41; Rx2 = 1.035;
Ny = 91; Ry = 4.50;
Nb = 121; Rb= 0.96;
Nc = 61; Rc= 1.00;
// Domain points
//+
Point(1) = {-10, -10, 0, 1.0};
//+
Point(2) = {10, -10, 0, 1.0};
//+
Point(3) = {40, -10, 0, 1.0};
//+
Point(4) = {-10, 10, 0, 1.0};
//+
Point(5) = {10, 10, 0, 1.0};
//+
Point(6) = {40, 10, 0, 1.0};
// Cylinder points
// radius r=0.5 so 0.5cos(45) = 0.35355339
//+
Point(7) = {-0.35355339, -0.35355339, 0, 1.0};
//+
Point(8) = {0.35355339, -0.35355339, 0, 1.0};
//+
Point(9) = {-0.35355339, 0.35355339, 0, 1.0};
//+
Point(10) = {0.35355339, 0.35355339, 0, 1.0};
//+
Point(11) = {0, -0, 0, 1.0};
//+

// Domain Lines
Line(1) = {1, 2}; Transfinite Curve {1} = Nx1 Using Progression Rx1;
//+
Line(2) = {2, 3}; Transfinite Curve {2} = Nx2 Using Progression Rx2;
//+
Line(3) = {4, 5}; Transfinite Curve {3} = Nx1 Using Progression Rx1;
//+
Line(4) = {5, 6}; Transfinite Curve {4} = Nx2 Using Progression Rx2;
//+
Line(5) = {1, 4}; Transfinite Curve {5} = Ny Using Bump Ry;
//+
Line(6) = {2, 5}; Transfinite Curve {6} = Ny Using Bump Ry;
//+
Line(7) = {3, 6}; Transfinite Curve {7} = Ny Using Bump Ry;

// Cylinder arc
//+
Circle(8) = {7, 11, 8}; Transfinite Curve {8} = Nc Using Progression Rc;
//+
Circle(9) = {8, 11, 10}; Transfinite Curve {9} = Ny Using Progression Rc;
//+
Circle(10) = {10, 11, 9}; Transfinite Curve {10} = Nc Using Progression Rc;
//+
Circle(11) = {9, 11, 7}; Transfinite Curve {11} = Ny Using Progression Rc;
//+

// diagonals block lines 
Line(12) = {1, 7}; Transfinite Curve {12} = Nb Using Progression Rb;
//+
Line(13) = {2, 8}; Transfinite Curve {13} = Nb Using Progression Rb;
//+
Line(14) = {5, 10}; Transfinite Curve {14} = Nb Using Progression Rb;
//+
Line(15) = {4, 9}; Transfinite Curve {15} = Nb Using Progression Rb;

// Surfaces
//+
Curve Loop(1) = {12, 8, -13, -1};
//+
Plane Surface(1) = {1};
//+
Curve Loop(2) = {13, 9, -14, -6};
//+
Plane Surface(2) = {2};
//+
Curve Loop(3) = {14, 10, -15, 3};
//+
Plane Surface(3) = {3};
//+
Curve Loop(4) = {15, 11, -12, 5};
//+
Plane Surface(4) = {4};
//+
Curve Loop(5) = {7, -4, -6, 2};
//+
Plane Surface(5) = {5};
//+
Transfinite Surface {1};
//+
Transfinite Surface {2};
//+
Transfinite Surface {3};
//+
Transfinite Surface {4};
//+
Transfinite Surface {5};
//+
Recombine Surface {1};
//+
Recombine Surface {2};
//+
Recombine Surface {3};
//+
Recombine Surface {4};
//+
Recombine Surface {5};
//+
Physical Surface("Fluid", 101) = {4, 1, 2, 3, 5};
//+
Physical Curve("Cylinder", 1005) = {8, 9, 10, 11};
//+
Physical Curve("Inlet", 1001) = {5};
//+
Physical Curve("Outlet", 1002) = {7};
//+
Physical Curve("Wall", 1003) = {4, 3, 1, 2};
//+
Save "cylinder.msh";

before trying your code, I would like to ask you to try a modification yourself. Use a well established element formulation for your benchmarks, such as the Taylor-Hood element.
From gmsh export a mesh with linear triangles. In getfem define a quadratic meshfem for velocities and a linear one for pressure.

mfv_fluid.set_classical_fem(2) # equivalent to "FEM_PK(2,2)" on triangles
mfp_fluid.set_classical_fem(1) # equivalent to "FEM_PK(2,1)" on triangles

Thank you! It seems to work better like this. I have a couple more questions:

  1. For quadrilateral elements, which combination of elements is considered stable?

  2. When I define a macro like this:

    md.add_macro("Convection(v)", "(rho_f*v.Grad_v)")
    
    

    and then use it as (Convection(v_previous)), does GetFEM correctly expand it to (rho_f*v_previous.Grad_v_previous)?

  3. Finally, if I stop a simulation before it’s finished and want to restart it, is it possible to retrieve the velocity and pressure fields from a .vtu file?

  1. Quadrilateral elements for Stokes flow are not as easy. There is an element known as “mini” but I am not sure if GetFEM implements it, because as far as I know we only have bubble shape functions for triangles/tetrahedrals but not for quads. @yves.renard is that correct?

See also:

  1. yes, exactly

  2. no, vtu is not an input/output format, vtu is suitable only for output. You can just dump the state of the model to a numpy file and reload it afterwards, with

 np.save("model_state.npy", md.from_variables())

and

 md.to_variables(np.load("model_state.npy"))

where md is yout model object.

Yes, I confirm. The mini element is not available, there is not bubble enriched quadrialteral element in getfem. Adding this element would be rather easy, but it has not been done yet.

However, Q2 x Q1 is also stable I think, and available.

hello, i was trying to use the FEM_PK_WITH_CUBIC_BUBBLE but i get the following error: “Only Lagrange fems can be used in interpolation”, what should i do instead ?

well what you actually miss is the syntax for projecting some field between different FE spaces

let’s say mfL is a Lagrange mesh_fem and mfNL is a non Lagrange one. You can still interpolate on mfL and the project the result onto mfNL with

V0 = md.interpolation("[U_mean, 0]", mfL)
mass_mat = gf.asm_mass_matrix(mim, mfNL)
Vasm =  gf.asm_generic(mim, 1, "v0*Test_t", -1,
                       "v0", False, mfL, V0,
                       "t", True, mfNL, np.zeros(mfNL.nbdof())
                       )
V = gf.linsolve_mumps(mass_mat, Vasm).T

by the way, you can also transfer interpolation results between reduced and unreduced mesh_fems using the methods

mf.reduce_vector(V)

and

mf.extend_vector(V)

see documentation at