19th OpenFOAM Workshop - Beijing, June 25-28, 2024

Getting started with
OpenFOAM-preCICE
for FSI simulations

Jun Chen & Gerasimos Chourdakis, University of Stuttgart
jun.chen@ipvs.uni-stuttgart.de

Uncoupled simulation

Import preCICE


                        import numpy
                        
                        
                        # generate mesh
                        n = 20
                        y = numpy.linspace(0, 1, n + 1)

                        dt = 0.01
                        t = 0

                        while True:
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          t = t + dt 
                          if(t > 0.1):
                            break
                    

Import preCICE


                        import numpy
                        import precice
                        
                        # generate mesh
                        n = 20
                        y = numpy.linspace(0, 1, n + 1)

                        dt = 0.01
                        t = 0

                        while True:
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          t = t + dt 
                          if(t > 0.1):
                            break
                    

Configure preCICE


                        import numpy
                        import precice
                        
                        # generate mesh
                        n = 20
                        y = numpy.linspace(0, 1, n + 1)

                        # preCICE setup
                        participant_name     = "Generator"
                        config_file_name     = "../precice-config.xml"
                        solver_process_index = 0
                        solver_process_size  = 1
                        participant = 
                            precice.Participant(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )

                        dt = 0.01
                        t = 0

                        while True:
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          t = t + dt 
                          if(t > 0.1):
                            break
                    

Define the coupling mesh


                        import numpy
                        import precice
                        
                        # generate mesh
                        n = 20
                        y = numpy.linspace(0, 1, n + 1)

                        # preCICE setup
                        participant_name     = "Generator"
                        config_file_name     = "../precice-config.xml"
                        solver_process_index = 0
                        solver_process_size  = 1
                        participant =
                            precice.Participant(
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                        
                        # Define the coupling mesh name
                        mesh_name  = "Generator-Mesh"
                        
                        # Define the coupling mesh
                        vertices   = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = participant.set_mesh_vertices(mesh_name, positions)
                        
                        dt = 0.01
                        t = 0

                        while True:
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          t = t + dt 
                          if(t > 0.1):
                            break
                    

Initialize and finalize preCICE


                        import numpy
                        import precice
                        
                        # generate mesh
                        n = 20
                        y = numpy.linspace(0, 1, n + 1)

                        # preCICE setup
                        participant_name     = "Generator"
                        config_file_name     = "../precice-config.xml"
                        solver_process_index = 0
                        solver_process_size  = 1
                        participant = 
                            precice.Participant(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                        
                        # Define the coupling mesh name
                        mesh_name = "Generator-Mesh"
                        
                        # Define the coupling mesh
                        vertices = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = participant.set_mesh_vertices(mesh_name, vertices)
                        
                        participant.initialize()

                        dt = 0.01
                        t = 0

                        while True:
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          t = t + dt 
                          if(t > 0.1):
                            break
                        
                        participant.finalize()
                    

Advance the coupling


                        import numpy
                        import precice
                        
                        # generate mesh
                        n = 20
                        y = numpy.linspace(0, 1, n + 1)

                        # preCICE setup
                        participant_name     = "Generator"
                        config_file_name     = "../precice-config.xml"
                        solver_process_index = 0
                        solver_process_size  = 1
                        participant =
                            precice.Participant(
                                              participant_name,
                                              config_file_name,
                                              solver_process_index,
                                              solver_process_size
                                            )
                    
                        # Define the coupling mesh name
                        mesh_name = "Generator-Mesh"
                        
                        # Define the coupling mesh
                        vertices = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = participant.set_mesh_vertices(mesh_name, vertices)
                        
                        participant.initialize()

                        dt = 0.01
                        t = 0

                        while True:
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          
                          
                          t = t + dt 
                          if(t > 0.1):
                            break
                        
                        participant.finalize()
                    

Advance the coupling


                        import numpy
                        import precice
                        
                        # generate mesh
                        n = 20
                        y = numpy.linspace(0, 1, n + 1)

                        # preCICE setup
                        participant_name     = "Generator"
                        config_file_name     = "../precice-config.xml"
                        solver_process_index = 0
                        solver_process_size  = 1
                        participant =
                            precice.Participant(
                                              participant_name,
                                              config_file_name,
                                              solver_process_index,
                                              solver_process_size
                                            )
                    
                        # Define the coupling mesh name
                        mesh_name = "Generator-Mesh"
                        
                        # Define the coupling mesh
                        vertices = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = participant.set_mesh_vertices(mesh_name, vertices)
                        
                        participant.initialize()

                        dt = 0.01
                        t = 0

                        while True:
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          participant.advance(dt)
                          
                          t = t + dt 
                          if(t > 0.1):
                            break
                        
                        participant.finalize()
                    

Advance the coupling


                        import numpy
                        import precice
                        
                        # generate mesh
                        n = 20
                        y = numpy.linspace(0, 1, n + 1)

                        # preCICE setup
                        participant_name     = "Generator"
                        config_file_name     = "../precice-config.xml"
                        solver_process_index = 0
                        solver_process_size  = 1
                        participant =
                            precice.Participant(
                                              participant_name,
                                              config_file_name,
                                              solver_process_index,
                                              solver_process_size
                                            )
                    
                        # Define the coupling mesh name
                        mesh_name = "Generator-Mesh"
                        
                        # Define the coupling mesh
                        vertices = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = participant.set_mesh_vertices(mesh_name, vertices)
                        
                        participant.initialize()

                        dt = 0.01
                        t = 0

                        while participant.is_coupling_ongoing():
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          participant.advance(dt)
                          
                          t = t + dt 
                        
                        
                        
                        participant.finalize()
                    

Advance the coupling


                        import numpy
                        import precice
                        
                        # generate mesh
                        n = 20
                        y = numpy.linspace(0, 1, n + 1)

                        # preCICE setup
                        participant_name     = "Generator"
                        config_file_name     = "../precice-config.xml"
                        solver_process_index = 0
                        solver_process_size  = 1
                        participant =
                            precice.Participant(
                                              participant_name,
                                              config_file_name,
                                              solver_process_index,
                                              solver_process_size
                                            )
                    
                        # Define the coupling mesh name
                        mesh_name = "Generator-Mesh"
                        
                        # Define the coupling mesh
                        vertices = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = participant.set_mesh_vertices(mesh_name, vertices)
                        
                        participant.initialize()

                        dt = 0.01
                        t = 0

                        while participant.is_coupling_ongoing():
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          participant.advance(dt)
                          precice_dt = participant.get_max_time_step_size()
                          
                          t = t + dt 
                        
                        
                        
                        interface.finalize()
                    

Advance the coupling


                        import numpy
                        import precice
                        
                        # generate mesh
                        n = 20
                        y = numpy.linspace(0, 1, n + 1)

                        # preCICE setup
                        participant_name     = "Generator"
                        config_file_name     = "../precice-config.xml"
                        solver_process_index = 0
                        solver_process_size  = 1
                        participant =
                            precice.Participant(
                                              participant_name,
                                              config_file_name,
                                              solver_process_index,
                                              solver_process_size
                                            )
                    
                        # Define the coupling mesh name
                        mesh_name = "Generator-Mesh"
                        
                        # Define the coupling mesh
                        vertices = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = participant.set_mesh_vertices(mesh_name, vertices)
                        
                        participant.initialize()
                        precice_dt = participant.get_max_time_step_size()

                        dt = 0.01
                        t = 0

                        while participant.is_coupling_ongoing():
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          participant.advance(dt)
                          precice_dt = participant.get_max_time_step_size()
                          
                          t = t + dt 
                        
                        interface.finalize()
                    
Not there yet, but let's run it
(similar changes in propagator.py - try it at home)

Nothing happening?

Did we forget something?

Write & read data


                        import numpy
                        import precice
                        
                        # generate mesh
                        n = 20
                        y = numpy.linspace(0, 1, n + 1)

                        # preCICE setup
                        participant_name     = "Generator"
                        config_file_name     = "../precice-config.xml"
                        solver_process_index = 0
                        solver_process_size  = 1
                        participant = 
                            precice.Participant(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                        
                        # Get the preCICE mesh name
                        mesh_name  = "Generator-Mesh"
                         
                        # Define the coupling mesh
                        vertices   = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = participant.set_mesh_vertices(mesh_name, vertices)
                        



                        
                        participant.initialize()
                        precice_dt = participant.get_max_time_step_size()

                        dt = 0.01
                        t = 0

                        while participant.is_coupling_ongoing():
                          dt = np.minimum(dt, precice_dt)
                          
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          

                          participant.advance(dt)
                          precice_dt = participant.get_max_time_step_size()

                          
                          t = t + dt 
                        
                        participant.finalize()
                    

Write & read data


                        import numpy
                        import precice
                        
                        # generate mesh
                        n = 20
                        y = numpy.linspace(0, 1, n + 1)

                        # preCICE setup
                        participant_name     = "Generator"
                        config_file_name     = "../precice-config.xml"
                        solver_process_index = 0
                        solver_process_size  = 1
                        participant = 
                            precice.Participant(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                        
                        # Get the preCICE mesh name
                        mesh_name  = "Generator-Mesh"
                         
                        # Define the coupling mesh
                        vertices   = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = participant.set_mesh_vertices(mesh_id, vertices)
                        
                        # Get the exchanged data name
                        data_name = "Data"
                        
                        participant.initialize()

                        dt = 0.01
                        t = 0

                        while participant.is_coupling_ongoing():
                          dt = np.minimum(dt, precice_dt)
                          
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          participant.write_data(mesh_name, data_name, vertex_ids, u)
                          precice_dt = participant.advance(dt)
                          # u[:, -1] = participant.read_data(mesh_name, data_name, vertex_ids, dt)
                          
                          t = t + dt 
                        
                        participant.finalize()
                    
It should work now!

Data is being transfered!

Most basic case: uni-directional, serial-explicit, nearest-neighbor mapping, ...

preCICE configuration

Visual representation of the precice-config.xml Visual representation of precice-config.xml using the config visualizer.

Configure OpenFOAM


                // fluid-openfoam/0/U
                    
                flap
                {
                    type            movingWallVelocity;
                    value           uniform (0 0 0);
                }
                

                // fluid-openfoam/0/pointDisplacement
                    
                flap
                {
                    type            fixedValue;
                    value           $internalField;
                }
                

                // fluid-openfoam/constant/dynamicMeshDict
                    
                solver      displacementLaplacian;
                

Load the OpenFOAM adapter


                // fluid-openfoam/system/controlDict
                functions
                {
                    preCICE_Adapter
                    {
                        type preciceAdapterFunctionObject;
                        libs ("libpreciceAdapterFunctionObject.so");
                    }
                }

                // Don't forget to build the adapter with Allwmake
                

Configure the OpenFOAM adapter


                // fluid-openfoam/system/preciceDict
                preciceConfig "../precice-config.xml";
                participant Fluid;

                modules (FSI);

                interfaces {
                  Interface1 {
                    mesh Fluid-Mesh;
                    patches (flap);
                    locations faceCenters;

                    readData (Displacement);

                    writeData (Force);
                  };
                };

                FSI {
                  rho rho [1 -3 0 0 0 0 0] 1;
                }
                

Configure the dealii adapter


                // solid-dealii/parameters.prm
                    
                subsection precice configuration
                # Cases: FSI3 or PF for perpendicular flap
                set Scenario = PF
                
                # Name of the precice configuration file
                set precice config-file = ../precice-config.xml
                
                # Name of the participant in the precice-config.xml file
                set Participant name = Solid
                
                # Name of the coupling mesh in the precice-config.xml file
                set Mesh name = Solid-Mesh
                
                # Name of the read data in the precice-config.xml file
                set Read data name = Force
                
                # Name of the write data in the precice-config.xml file
                set Write data name = Displacement
                

Configure preCICE

Configure preCICE


                      <data:vector name="Force" />
                      <data:vector name="Displacement" />

                      <mesh name="Fluid-Mesh" dimensions="2">
                        <use-data name="Force" />
                        <use-data name="Displacement" />
                      </mesh>

                      <mesh name="Solid-Mesh" dimensions="2">
                        <use-data name="Displacement" />
                        <use-data name="Force" />
                      </mesh>

                      <participant name="Fluid">
                        <export:vtk directory="results" />
                        <provide-mesh name="Fluid-Mesh" />
                        <receive-mesh name="Solid-Mesh" from="Solid" />
                        <write-data name="Force" mesh="Fluid-Mesh" />
                        <read-data name="Displacement" mesh="Fluid-Mesh" />
                        <mapping:rbf-thin-plate-splines
                          direction="write"
                          from="Fluid-Mesh"
                          to="Solid-Mesh"
                          constraint="conservative" />
                        <mapping:rbf-thin-plate-splines
                          direction="read"
                          from="Solid-Mesh"
                          to="Fluid-Mesh"
                          constraint="consistent" />
                      </participant>

                      <participant name="Solid">
                        <provide-mesh name="Solid-Mesh" />
                        <receive-data name="Displacement" mesh="Solid-Mesh" />
                        <read-data name="Force" mesh="Solid-Mesh" />
                        <watch-point mesh="Solid-Mesh" name="Flap-Tip" coordinate="0.0;1" />
                      </participant>

                      <m2n:sockets acceptor="Fluid" connector="Solid" exchange-directory=".." />

                      <coupling-scheme:serial-explicit>
                        <participants first="Fluid" second="Solid" />
                        <max-time value="1.5" />
                        <time-window-size value="0.01" />
                        <exchange data="Force" mesh="Solid-Mesh" from="Fluid" to="Solid" />
                        <exchange data="Displacement" mesh="Solid-Mesh" from="Solid" to="Fluid" />
                      </coupling-scheme:serial-explicit>
                    
Notice the serial-explicit coupling scheme

When we decrease the solid density further to 1, the participants have stronger coupling, then:

What we did so far: serial-explicit scheme

Let's try an implicit coupling scheme

Configure the coupling scheme


                        <coupling-scheme:serial-implicit>
                          <participants first="Fluid" second="Solid" />
                          <max-time value="1.5" />
                          <time-window-size value="0.01" />
                          <exchange data="Force" mesh="Solid-Mesh" from="Fluid" to="Solid" />
                          <exchange data="Displacement" mesh="Solid-Mesh" from="Solid" to="Fluid" />
                          <relative-convergence-measure limit="5e-3" data="Displacement" mesh="Solid-Mesh" />
                          <relative-convergence-measure limit="5e-3" data="Force" mesh="Solid-Mesh" />
                          <max-iterations value="50" />
                        </coupling-scheme:serial-implicit>
                    
Only change from explicit to implicit coupling
With solid density equal to 42:

Average iteration number per time step: 3.41
With solid density equal to 1: break again!

Still not enough stability for strong coupling!

Configure the coupling scheme


                        <coupling-scheme:serial-implicit>
                          <participants first="Fluid" second="Solid" />
                          <max-time value="1.5" />
                          <time-window-size value="0.01" />
                          <exchange data="Force" mesh="Solid-Mesh" from="Fluid" to="Solid" />
                          <exchange data="Displacement" mesh="Solid-Mesh" from="Solid" to="Fluid" />
                          <relative-convergence-measure limit="5e-3" data="Displacement" mesh="Solid-Mesh" />
                          <relative-convergence-measure limit="5e-3" data="Force" mesh="Solid-Mesh" />
                          <max-iterations value="50" />
                          <acceleration:constant>
                            <relaxation value="0.5" />
                          </acceleration:constant>
                        </coupling-scheme:serial-implicit>
                    
Simplest acceleration: constant under-relaxation
With solid density equal to 1:

Average iteration number per time step: 5.27

Too slow! Can we do better?

Improvement 1: Aitken under-relaxation


                        <acceleration:aitken>
                          <data name="Displacement" mesh="Solid-Mesh" />
                          <initial-relaxation value="0.5" />
                        </acceleration:aitken>
                    

Improvement 2: Anderson acceleration


                      <acceleration:IQN-ILS>
                        <data name="Displacement" mesh="Solid-Mesh" />
                        <initial-relaxation value="0.5" />
                        <preconditioner type="residual-sum" />
                        <filter type="QR2" limit="1e-2" />
                        <max-used-iterations value="100" />
                        <time-windows-reused value="15" />
                      </acceleration:IQN-ILS>
                    

There are also parallel schemes

Can use fields from both / all participants

Improvement 3: Parallel coupling schemes


                    <coupling-scheme:parallel-implicit>
                      <time-window-size value="0.01" />
                      <max-time value="1.5" />
                      <participants first="Fluid" second="Solid" />
                      <exchange data="Force" mesh="Solid-Mesh" from="Fluid" to="Solid" />
                      <exchange data="Displacement" mesh="Solid-Mesh" from="Solid" to="Fluid" />
                      <max-iterations value="50" />
                      <relative-convergence-measure limit="5e-3" data="Displacement" mesh="Solid-Mesh" />
                      <relative-convergence-measure limit="5e-3" data="Force" mesh="Solid-Mesh" />
                      <acceleration:IQN-ILS>
                        <data name="Displacement" mesh="Solid-Mesh" />
                        <data name="Force" mesh="Solid-Mesh" />
                        <initial-relaxation value="0.5" />
                        <preconditioner type="residual-sum" />
                        <filter type="QR2" limit="1e-2" />
                        <max-used-iterations value="100" />
                        <time-windows-reused value="15" />
                      </acceleration:IQN-ILS>
                    </coupling-scheme:parallel-implicit>
                    
density = 42 density = 1
serial-explicit div div
se-implicit 3.41 div
se-im-const 4.48 5.27
se-im-Aitken 3.74 5.3
se-im-ILS 2.95 3.07
parallel-im-ILS 2.46 2.33
Beware: Configuration not fine-tuned, not rigorous comparison, application context.

Rigorous comparison from the literature

Source: Dissertation of Benjamin Uekermann (2016)

Rigorous comparison from the literature

Source: Dissertation of Benjamin Uekermann (2016)