16th OpenFOAM Workshop - June 11, 2021 - Training session

Flexible & efficient multiphysics simulations with the coupling library preCICE

Gerasimos Chourdakis, Technical University of Munich
chourdak@in.tum.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
                        interface = 
                            precice.Interface(  
                                               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
                        interface = 
                            precice.Interface(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                        
                        # Get the preCICE mesh id
                        mesh_name  = "Generator-Mesh"
                        mesh_id    = interface.get_mesh_id(mesh_name)
                        
                        # Define the coupling mesh
                        vertices   = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = interface.set_mesh_vertices(mesh_id, vertices)
                        
                        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
                        interface = 
                            precice.Interface(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                        
                        # Get the preCICE mesh id
                        mesh_name  = "Generator-Mesh"
                        mesh_id    = interface.get_mesh_id(mesh_name)
                        
                        # Define the coupling mesh
                        vertices   = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = interface.set_mesh_vertices(mesh_id, vertices)
                        
                        interface.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
                        
                        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
                        interface = 
                            precice.Interface(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                                             
                        # Get the preCICE mesh id
                        mesh_name  = "Generator-Mesh"
                        mesh_id    = interface.get_mesh_id(mesh_name)
                         
                        # Define the coupling mesh
                        vertices   = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = interface.set_mesh_vertices(mesh_id, vertices)
                        
                        interface.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
                        
                        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
                        interface = 
                            precice.Interface(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                        
                         # Get the preCICE mesh id
                         mesh_name  = "Generator-Mesh"
                         mesh_id    = interface.get_mesh_id(mesh_name)
                         
                         # Define the coupling mesh
                         vertices   = [[1, y0] for y0 in y[:-1]]
                         vertex_ids = interface.set_mesh_vertices(mesh_id, vertices)
                        
                        interface.initialize()

                        dt = 0.01
                        t = 0

                        while True:
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          interface.advance(dt)
                          
                          t = t + dt 
                          if(t > 0.1):
                            break
                        
                        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
                        interface = 
                            precice.Interface(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                        
                        # Get the preCICE mesh id
                        mesh_name  = "Generator-Mesh"
                        mesh_id    = interface.get_mesh_id(mesh_name)
                        
                        # Define the coupling mesh
                        vertices   = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = interface.set_mesh_vertices(mesh_id, vertices)
                        
                        interface.initialize()

                        dt = 0.01
                        t = 0

                        while interface.is_coupling_ongoing():
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          interface.advance(dt)
                          
                          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
                        interface = 
                            precice.Interface(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                        
                        # Get the preCICE mesh id
                        mesh_name  = "Generator-Mesh"
                        mesh_id    = interface.get_mesh_id(mesh_name)
                        
                        # Define the coupling mesh
                        vertices   = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = interface.set_mesh_vertices(mesh_id, vertices)
                        
                        interface.initialize()

                        dt = 0.01
                        t = 0

                        while interface.is_coupling_ongoing():
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          precice_dt = interface.advance(dt)
                          
                          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
                        interface = 
                            precice.Interface(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                        
                        # Get the preCICE mesh id
                        mesh_name  = "Generator-Mesh"
                        mesh_id    = interface.get_mesh_id(mesh_name)
                        
                        # Define the coupling mesh
                        vertices   = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = interface.set_mesh_vertices(mesh_id, vertices)
                        
                        precice_dt = interface.initialize()

                        dt = 0.01
                        t = 0

                        while interface.is_coupling_ongoing():
                          dt = np.minimum(dt, precice_dt)
                          
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          precice_dt = interface.advance(dt)
                          
                          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
                        interface = 
                            precice.Interface(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                        
                        # Get the preCICE mesh id
                        mesh_name  = "Generator-Mesh"
                        mesh_id    = interface.get_mesh_id(mesh_name)
                         
                        # Define the coupling mesh
                        vertices   = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = interface.set_mesh_vertices(mesh_id, vertices)
                        



                        
                        precice_dt = interface.initialize()

                        dt = 0.01
                        t = 0

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

                          precice_dt = interface.advance(dt)

                          
                          t = t + dt 
                        
                        interface.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
                        interface = 
                            precice.Interface(  
                                               participant_name,
                                               config_file_name,
                                               solver_process_index,
                                               solver_process_size
                                             )
                        
                        # Get the preCICE mesh id
                        mesh_name  = "Generator-Mesh"
                        mesh_id    = interface.get_mesh_id(mesh_name)
                         
                        # Define the coupling mesh
                        vertices   = [[1, y0] for y0 in y[:-1]]
                        vertex_ids = interface.set_mesh_vertices(mesh_id, vertices)
                        
                        # Get the exchanged data id
                        data_name = "Data"
                        data_id = interface.get_data_id(data_name, mesh_id)
                        
                        precice_dt = interface.initialize()

                        dt = 0.01
                        t = 0

                        while interface.is_coupling_ongoing():
                          dt = np.minimum(dt, precice_dt)
                          
                          print("Generating data")
                          u = 1 - 2 * numpy.random.rand(n)
                          
                          interface.write_block_scalar_data(data_id, vertex_ids, u)
                          precice_dt = interface.advance(dt)
                          # interface.read_block_scalar_data(data_id, vertex_ids, u)
                          
                          t = t + dt 
                        
                        interface.finalize()
                    
It should work now!

Data is being transfered!

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

Side note: Subcycling

preCICE configuration file

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

                        <precice-configuration>
                          <solver-interface dimensions="2">

                          <data:scalar name="Data"/>

                          <mesh name="Generator-Mesh">
                            <use-data name="Data"/>
                          </mesh>

                          <mesh name="Propagator-Mesh">
                            <use-data name="Data" />
                          </mesh>
                          
                          <participant name="Generator">
                            <use-mesh name="Generator-Mesh" provide="yes"/>
                            <write-data name="Data" mesh="Generator-Mesh"/>
                          </participant>

                          <participant name="Propagator">
                            <use-mesh name="Generator-Mesh" from="Generator"/>
                            <use-mesh name="Propagator-Mesh" provide="yes"/>
                            <mapping:nearest-neighbor direction="read"
                              from="Generator-Mesh" to="Propagator-Mesh"
                              constraint="consistent"/>
                            <read-data name="Data" mesh="Propagator-Mesh" />
                          </participant>    

                          <m2n:sockets from="Generator" to="Propagator" exchange-directory=".."/>

                          <coupling-scheme:serial-explicit>
                            <participants first="Generator" second="Propagator"/>
                            <time-window-size value="0.01"/>
                            <max-time value="0.1"/>
                            <exchange data="Data" mesh="Generator-Mesh"
                              from="Generator" to="Propagator"/>
                          </coupling-scheme:serial-explicit>
                          
                          </solver-interface>
                        </precice-configuration>
                    

Enabling the adapter


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

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

OpenFOAM adapter config


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

                participant Fluid;

                modules (CHT);

                interfaces
                {
                  Interface1
                  {
                    mesh      Fluid-Mesh;
                    patches   (interface);
                    
                    readData
                    (
                      Heat-Flux
                    );
                    
                    writeData
                    (
                      Temperature
                    );
                  };
                };
                

                // solid-openfoam/system/preciceDict
                    
                preciceConfig "../precice-config.xml";

                participant Solid;

                modules (CHT);

                interfaces
                {
                  Interface1
                  {
                    mesh      Solid-Mesh;
                    patches   (interface);
                    
                    readData
                    (
                      Temperature
                    );
                    
                    writeData
                    (
                      Heat-Flux
                    );
                  };
                };

                CHT
                {
                   k   [ 1  1 -3 -1 0 0 0 ] 100;
                   solverType "basic";
                };
                

OpenFOAM boundary conditions


                // fluid-openfoam/0/T
                    
                internalField uniform 300;

                boundaryField
                {
                  interface
                  {
                    type      fixedGradient;
                    gradient  uniform 0;
                  }
                  inlet
                  {
                    type      fixedValue;
                    value     $internalField;
                  }
                  outlet
                  {
                    type      zeroGradient;
                  }
                  // ...
                }
                

                // solid-openfoam/0/T
                    
                internalField uniform 310;

                boundaryField
                {
                  interface
                  {
                    type      fixedValue;
                    value     $internalField;
                  }
                  left
                  {
                    type      zeroGradient;
                  }
                  // ...
                }
                

precice-config.xml


                        <precice-configuration>

                            <solver-interface dimensions="2">

                            <data:scalar name="Temperature"/>
                            <data:scalar name="Heat-Flux"/>

                            <mesh name="Fluid-Mesh">
                                <use-data name="Temperature"/>
                                <use-data name="Heat-Flux"/>
                            </mesh>

                            <mesh name="Solid-Mesh">
                                <use-data name="Temperature"/>
                                <use-data name="Heat-Flux"/>
                            </mesh>

                            <participant name="Fluid">
                                <use-mesh name="Fluid-Mesh" provide="yes"/>
                                <use-mesh name="Solid-Mesh" from="Solid"/>
                                <read-data name="Heat-Flux" mesh="Fluid-Mesh"/>
                                <write-data name="Temperature" mesh="Fluid-Mesh"/>
                                <mapping:nearest-neighbor direction="read"
                                  from="Solid-Mesh" to="Fluid-Mesh"
                                  constraint="consistent"/>
                            </participant>

                            <participant name="Solid">
                                <use-mesh name="Fluid-Mesh" from="Fluid"/>
                                <use-mesh name="Solid-Mesh" provide="yes"/>
                                <read-data name="Temperature" mesh="Solid-Mesh"/>
                                <write-data name="Heat-Flux" mesh="Solid-Mesh"/>
                                <mapping:nearest-neighbor direction="read"
                                  from="Fluid-Mesh" to="Solid-Mesh"
                                  constraint="consistent"/>
                            </participant>

                            <m2n:sockets from="Fluid" to="Solid" exchange-directory=".." />
                            
                            <coupling-scheme:serial-implicit>
                              <time-window-size value="0.01" />
                              <max-time value="1" />
                              <max-iterations value="30" />
                              <participants first="Fluid" second="Solid" />
                              <exchange data="Temperature" mesh="Fluid-Mesh" from="Fluid" to="Solid" />
                              <exchange data="Heat-Flux" mesh="Solid-Mesh" from="Solid" to="Fluid" />
                              <relative-convergence-measure limit="1.0e-5"
                                data="Temperature" mesh="Fluid-Mesh" />
                              <acceleration:aitken>
                                <data mesh="Solid-Mesh" name="Heat-Flux" />
                                <initial-relaxation value="0.5" />
                              </acceleration:aitken>
                            </coupling-scheme:serial-implicit>

                            </solver-interface>

                        </precice-configuration>
                    

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;
                

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 deal.II 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
                end
                

Configure preCICE


                    <solver-interface dimensions="2">
                      <data:vector name="Force" />
                      <data:vector name="Displacement" />
                    
                      <mesh name="Fluid-Mesh">
                        <use-data name="Force" />
                        <use-data name="Displacement" />
                      </mesh>
                    
                      <mesh name="Solid-Mesh">
                        <use-data name="Displacement" />
                        <use-data name="Force" />
                      </mesh>
                    
                      <participant name="Fluid">
                        <use-mesh name="Fluid-Mesh" provide="yes" />
                        <use-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">
                        <use-mesh name="Solid-Mesh" provide="yes" />
                        <write-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 from="Fluid" to="Solid" exchange-directory=".." />
                    
                      <coupling-scheme:parallel-implicit>
                        <time-window-size value="0.01" />
                        <max-time value="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" />
                          <preconditioner type="residual-sum" />
                          <filter type="QR2" limit="1e-2" />
                          <initial-relaxation value="0.5" />
                          <max-used-iterations value="100" />
                          <time-windows-reused value="15" />
                        </acceleration:IQN-ILS>
                      </coupling-scheme:parallel-implicit>
                    </solver-interface>
                    </precice-configuration>
                    

Linear solid model

How is the tip moving?


                            <participant name="Solid">
                              ...
                              <watch-point mesh="Solid-Mesh"
                                name="Flap-Tip"
                                coordinate="0.0;1" />
                            </participant>
                            

                              ./plot-displacement.sh
                            

What if we use other solver combinations?


Notes for differences: The CalculiX adapter only supports linear finite elements (deal.II uses 4th order, FEniCS 2nd order). SU2 models a compressible fluid, OpenFOAM and Nutils an incompressible one.

See also: Turek-Hron FSI3 benchmark