Blog : Mettre une sphère dans un tube

Cet article fait suite à la série de simulations sur l’allée de tourbillons de Von Kármán. Cette fois-ci, nous étendons le domaine en 3D et générons le maillage de calcul à l’aide de snappyHexMesh. La géométrie étant relativement simple, nous utilisons les surfaces recherchables (searchable surfaces) intégrées à OpenFOAM au lieu de fichiers STL externes.

Géométrie et maillage

Cette simulation est une extension en 3D de la classique allée de tourbillons de Von Kármán. Au lieu d’une géométrie plane en 2D, nous utilisons un tube cylindrique d’un diamètre de 4D et y plaçons une sphère de diamètre D. Nous appliquons une vitesse uniforme en entrée et observons le développement tridimensionnel de l’écoulement dans le sillage.

Scheme of the simulation

Le maillage est généré avec snappyHexMesh. Puisque notre domaine est constitué de formes géométriques de base, nous utilisons des objets géométriques intégrés (searchableCylinder et searchableSphere) plutôt que d’importer des fichiers STL externes :

geometry
{
    tube
    {
        type searchableCylinder;
        point1 (0 0 -100);
        point2 (0 0 100);
        radius #eval "2*$D";
    }

    sphere
    {
        type searchableSphere;
        origin (0 0 0);
        radius #eval "0.5*$D";
    }
}

Pour l’étape de maillage créneaux (castellated mesh), nous ne raffinons que les limites des surfaces. Les formes géométriques étant lisses et continues, l’extraction des arêtes n’est pas nécessaire (même l’entrée et la sortie sont définies par la boîte englobante du maillage de fond) :

    refinementSurfaces
    {
        sphere
        {
            level (1 1);
        }

        tube
        {
            level (1 1);
        }
    }

Pour résoudre les gradients de la couche limite le long des surfaces lisses, nous ajoutons des couches d’inflation prismatiques :

    layers
    {
        sphere
        {
            nSurfaceLayers 3;
        }

        tube
        {
            nSurfaceLayers 3;
        }
    }

Le reste du fichier snappyHexMeshDict utilise des paramètres standards.

L’exécution de snappyHexMesh produit un maillage propre avec une non-orthogonalité maximale d’environ 50° :

Mesh zoom around the sphere

Note : Pour éviter que ParaView n’affiche de manière erronée les cellules limites coupées, l’option de découpe Crinkle clip est activée.

Checking geometry...
    Overall domain bounding box (-2 -2 -4) (2 2 12)
    Mesh has 3 geometric (non-empty/wedge) directions (1 1 1)
    Mesh has 3 solution (non-empty) directions (1 1 1)
    Boundary openness (-3.15769e-16 2.9073e-16 7.81195e-17) OK.
    Max cell openness = 4.07388e-16 OK.
    Max aspect ratio = 4.04672 OK.
    Minimum face area = 0.000805246. Maximum face area = 0.013633.  Face area magnitudes OK.
    Min volume = 4.0276e-05. Max volume = 0.00125246.  Total volume = 200.51.  Cell volumes OK.
    Mesh non-orthogonality Max: 50.0132 average: 7.10234
    Non-orthogonality check OK.
    Face pyramids OK.
    Max skewness = 0.377543 OK.
    Coupled point location match (average 0) OK.

Conditions d’écoulement et propriétés du fluide

Comme pour le cas 2D, nous spécifions une vitesse constante en entrée (Uin) et faisons varier la viscosité cinématique (nu) pour atteindre le nombre de Reynolds ciblé (Re). Ceci est configuré de manière dynamique via l’entrée #eval dans constant/transportProperties :

#include "<case>/settings"

transportModel Newtonian;

nu              #eval "$Uin*$D/$Re";

Les variables Uin, D et Re sont définies dans le dictionnaire settings à l’intérieur du dossier du cas.

Visualisation de l’écoulement

Dans les simulations 2D, tracer les coefficients de force ou les cartes de contours de vitesse sur le plan médian fournit une image claire du champ d’écoulement. Cependant, en 3D, les coupes dans le plan médian ne parviennent souvent pas à capturer le détachement tourbillonnaire complexe autour de la sphère.

Une approche de visualisation bien supérieure consiste à tracer les lignes de courant en 3D.

Génération des lignes de courant

Approche sous ParaView

ParaView propose deux méthodes principales pour l’ensemencement (seeding) des lignes de courant :

  1. Stream Tracer : Place les points de départ en utilisant des formes géométriques de base (ligne ou sphère).
  2. Stream Tracer With Custom Source : Utilise une géométrie source personnalisée pour définir l’emplacement des points de départ.

Stream tracer

Pour étudier les structures d’écoulement prenant naissance juste devant l’obstacle, il est utile d’ensemencer les lignes de courant à l’intérieur d’une zone circulaire sur le plan d’entrée correspondant à la projection de la sphère. Nous pouvons créer cette source d’ensemencement personnalisée dans ParaView grâce au pipeline suivant :

  1. Extract Block: Isole le patch de la limite d’entrée.
  2. Delaunay 2D: Triangule la géométrie du patch pour la préparer au raffinement.
  3. Loop Subdivision: Raffine le maillage de surface triangulé.
  4. Clip (Cylinder): Découpe le patch raffiné pour créer un disque d’ensemencement circulaire du rayon souhaité.

Stream tracer with Custom Source

En réduisant le rayon de cette source personnalisée, nous pouvons visualiser clairement les motifs tourbillonnants complexes qui se forment dans le sillage de la sphère :

Stream tracer with Custom Source zoom

Enfin, l’application du filtre Stream Tracer permet de produire des animations fluides de l’écoulement :

Objet de fonction OpenFOAM streamLine

Les lignes de courant peuvent également être calculées directement pendant l’exécution ou en post-traitement à l’aide de l’objet de fonction streamLine d’OpenFOAM. Cet objet de fonction génère des particules lagrangiennes, suit leurs trajectoires à travers le champ de vitesse et enregistre les parcours résultants au format VTK.

Cependant, les options d’ensemencement intégrées sont assez limitées. L’option native la plus proche est patchSeed, qui distribue un nombre fixe de points sur l’ensemble d’un patch limite. Si on l’applique directement à notre patch d’entrée, patchSeed génère de nombreuses lignes de courant rectilignes sans grand intérêt près des parois extérieures du tube.

Pour restreindre l’ensemencement à une région spécifique, il nous faut une méthode du type faceZoneSeed. En combinant cela avec topoSet, nous pouvons définir une petite sous-zone sur l’entrée.

Implémentation de faceZoneSeedSet

L’examen de la classe patchSeedSet d’OpenFOAM montre qu’elle s’appuie sur triangulatedPatch pour distribuer les points d’ensemencement sur les faces d’un patch. Puisqu’une zone de faces (faceZone) peut être évaluée de manière similaire, nous pouvons facilement implémenter une classe personnalisée faceZoneSeedSet.

Nous adaptons patchSeedSet en remplaçant patchSet_ par faceZoneName_ et en supprimant selectedLocations_ :

class faceZoneSeedSet
:
    public sampledSet
{
    // Private Data
        //- Face zone name to sample
        const word faceZoneName_;
        
        //- Maximum number of zone faces to seed
        const label maxPoints_;
        
        //- Random number generator
        autoPtr<Random> rndGenPtr_;
        
    // Private Member Functions
        //- Calculate distribution points on the face zone
        void calcZoneSamples
        (
            const label nAvailable,
            const label nZonePoints,
            DynamicList<point>& samplingPts,
            DynamicList<label>& samplingFaces,
            DynamicList<label>& samplingSegments,
            DynamicList<scalar>& samplingCurveDist
        );

public:
    //- Runtime type information
    TypeName("faceZoneSeed");
    
    // Constructors
    faceZoneSeedSet
    (
        const word& name,
        const polyMesh& mesh,
        const meshSearch& searchEngine,
        const dictionary& dict
    );
};

Le positionnement des points d’ensemencement s’effectue dans calcZoneSamples, qui simule calcPatchSamples de la classe patchSeedSet :

void Foam::faceZoneSeedSet::calcZoneSamples
(
    const label nAvailable,
    const label nZonePoints,
    DynamicList<point>& samplingPts,
    DynamicList<label>& samplingFaces,
    DynamicList<label>& samplingSegments,
    DynamicList<scalar>& samplingCurveDist
)
{
    if (nAvailable < 1) return;

    Random& rndGen = *rndGenPtr_;

    globalIndex globalSampleNumbers(nAvailable);
    label nGlobalPatchPoints = returnReduce(nZonePoints, sumOp<label>());

    point pt;
    label facei;

    const faceZone& fz = mesh().faceZones()[faceZoneName_];
    triangulatedFaceZone tfz(fz);
    
    const label np = nAvailable * fz.size() / scalar(nGlobalPatchPoints);
    for (label i = 0; i < np; ++i)
    {
        tfz.randomLocalPoint(rndGen, pt, facei);
    
        samplingPts.append(pt);
        samplingFaces.append(fz[facei]);
        samplingSegments.append(0);
        samplingCurveDist.append(globalSampleNumbers.toGlobal(i));
    }
}

L’algorithme appelle randomLocalPoint via triangulatedFaceZone. Puisque les particules de lignes de courant résident dans le volume des cellules, nous associons les faces aux cellules intérieures à l’aide de faceOwner():

labelList samplingCells(samplingFaces.size(), -1);

forAll(samplingFaces, i)
{
    samplingCells[i] = mesh().faceOwner()[samplingFaces[i]];
}

Le code source complet des classes est disponible dans le dépôt d’exemples.

Configuration et Résultats

Nous configurons topoSetDict pour définir la faceZone cible (smallInlet) :

actions
(
  {
    name f0;
    type faceSet;
    action new;
    source patchToFace;
    sourceInfo
    {
      patch inlet;
    }
  }

  {
    name f0;
    type faceSet;
    action subset;
    source cylinderToFace;
    sourceInfo
    {
      point1 (0 0 -100);
      point2 (0 0 100);
      radius 0.2;
    }
  }

  {
    name smallInlet;
    type faceZoneSet;
    action new;
    source setToFaceZone;
    sourceInfo
    {
      faceSet f0;
    }
  }
);

Et la configuration suivante pour l’objet de fonction streamLine :

type        streamLine;
libs        (fieldFunctionObjects libfaceZoneSeed);
setFormat   vtk;

direction   forward;
fields      (p U);
lifeTime    10000;
nSubCycle   5;
cloud       particleTracks;

seedSampleSet
{
    type        faceZoneSeed;
    axis        xyz;
    zone        smallInlet;
    maxPoints   200;
}

L’exécution de l’objet de fonction génère les trajectoires de lignes de courant suivantes :

streamLine function object output

Note : Le maillage de fond au niveau de l’entrée étant relativement grossier, la limite de la zone d’ensemencement (faceZone) apparaît crantée/carrée. Dans ParaView, le fait de raffiner et de trianguler le patch extrait avant la découpe produit un profil d’ensemencement circulaire nettement plus lisse.

Les fichiers du cas de simulation sont disponibles dans le dépôt d’exemples.