2 MEDCoupling / MEDLoader - Exemple complet 1 - Agitateur
3 -------------------------------------------------------
5 Nous partons ici d'un fichier :download:`agitateur.med <data/agitateur.med>` ayant le contenu suivant :
7 .. image:: images/agitateur.jpg
9 Il s'agit du résultat d'un petit calcul diphasique : l'agitateur magnétique en vert (repéré seulement par un champ
10 aux cellules, et n'ayant *pas* de maillage propre) tourne d'un pas de temps à l'autre au
11 sein d'une phase liquide. Deux gouttes de liquide chutent pendant ce temps vers l'interface air/eau (en gris).
13 Le but de l'exercice est de calculer le couple appliqué sur cet agitateur, qui est la pièce mécanique entraînant la
14 partie basse du fluide.
19 L'objectif est de donner un exemple complet de post-traitement non trivial à partir d'un fichier MED.
21 Début de l'implémentation
22 ~~~~~~~~~~~~~~~~~~~~~~~~~
24 Pour commencer l'exercice importer tout le module python ``MEDLoader`` (qui inclut ``MEDCoupling``).
25 Importer aussi ``numpy``. ::
27 import MEDLoader as ml
30 Extraction des maillages et champs avec l'API avancée
31 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33 Avec l'API avancée lire tout le fichier "agitateur.med" et afficher tous les pas de temps du 1er champ. ::
35 data = ml.MEDFileData("agitateur.med")
36 ts = data.getFields()[0].getTimeSteps()
39 Récupérer le maillage de l'agitateur (en vert) au pas de temps (2,-1) (cf. ts).
40 La position de l'agitateur est définie par un champ sur le maillage global du système et n'a pas de maillage propre.
41 Il faut donc utiliser le champ aux cellules "DISTANCE_INTERFACE_ELEM_BODY_ELEM_DOM"
42 et ne sélectionner que la partie du champ ayant une valeur entre dans ``[0.,1.]``. Mettre les identifiants
43 de cellules correspondant dans ``ids`` : ::
45 fMts = data.getFields()["DISTANCE_INTERFACE_ELEM_BODY_ELEM_DOM"]
47 fMc = f1ts.getFieldAtLevel(ml.ON_CELLS,0)
49 arr.getMinMaxPerComponent() # just to see the field variation range per component
50 ids = arr.findIdsInRange(0.,1.)
53 A l'aide du champ "PRESSION_ELEM_DOM" trouver le champ de pression 3D qu'applique l'agitateur. Mettre le résultat dans
54 ``pressOnAgitateur``. ::
56 pressMts = data.getFields()["PRESSION_ELEM_DOM"]
57 press1ts = pressMts[(2,-1)]
58 pressMc = press1ts.getFieldAtLevel(ml.ON_CELLS,0)
59 pressOnAgitateurMc = pressMc[ids]
61 Supprimer les noeuds inutiles de ``pressOnAgitateurMc.getMesh()`` : ::
63 pressOnAgitateurMc.getMesh().zipCoords()
65 Passer d'un champ aux cellules 3D à un champ surfacique 3D
66 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68 Deduire le champ 3D de pression sur la *peau* de l'agitateur.
69 Pour ce faire passer par le maillage descendant ``MEDCouplingUMesh.buildDescendingConnectivity()``. ::
71 agitateurMesh3DMc = pressOnAgitateurMc.getMesh()
72 m3DSurf,desc,descI,revDesc,revDescI = agitateurMesh3DMc.buildDescendingConnectivity()
73 nbOf3DCellSharing = revDescI.deltaShiftIndex()
74 ids2 = nbOf3DCellSharing.findIdsEqual(1) # Cells with only one neighbor are on the boundary, i.e. on the skin
75 agitateurSkinMc = m3DSurf[ids2]
76 offsetsOfTupleIdsInField = revDescI[ids2]
77 tupleIdsInField = revDesc[offsetsOfTupleIdsInField]
78 pressOnSkinAgitateurMc = pressOnAgitateurMc[tupleIdsInField]
79 pressOnSkinAgitateurMc.setMesh(agitateurSkinMc)
84 Calculer le champ vectoriel de force sur la peau de l'agitateur en multipliant pour chaque cellule
85 la pression par la surface et ensuite par le vecteur normal.
86 La pression est en bar, la convertir au préalable en pascal (Pa). ::
88 pressSkin = pressOnSkinAgitateurMc.getArray()
89 pressSkin *= 1e5 # conversion from bar to Pa
90 areaSkin = agitateurSkinMc.getMeasureField(True).getArray()
91 forceSkin = pressSkin*areaSkin
92 normalSkin = agitateurSkinMc.buildOrthogonalField().getArray()
93 forceVectSkin = forceSkin*normalSkin
95 Voici maintenant le premier calcul du moment au centre de masse de l'agitateur :
97 Pour faire ce 1er calcul de couple exercé sur l'agitateur, calculons la position du centre de masse de l'agitateur.
98 Calculer le polyèdre représentant l'enveloppe du maillage 3D de l'agitateur ``agitateurMesh3DMc``
99 (utiliser ``MEDCouplingUMesh.buildSpreadZonesWithPoly()``). ::
101 singlePolyhedron = agitateurMesh3DMc.buildSpreadZonesWithPoly()
102 singlePolyhedron.orientCorrectlyPolyhedrons()
103 centerOfMass = singlePolyhedron.computeCellCenterOfMass()
105 .. note:: L'appel à ``MEDCouplingUMesh.orientCorrectlyPolyhedrons()`` n'est pas obligatoire mais conseillé car
106 si par malheur le polyhèdre est mal orienté, son barycentre sera incorrect !
108 Calculer pour chaque cellule de la peau de l'agitateur le moment par rapport au centre de masse ``centerOfMass``
110 Pour ce faire calculer ``posSkin`` le ``DataArrayDouble`` donnant pour chaque cellule de la peau de l'agitateur
111 le vecteur ``centerOfMass`` -> ``G``, avec ``G`` le barycentre de la cellule courante. ::
113 barySkin=agitateurSkinMc.computeCellCenterOfMass()
114 posSkin = barySkin-centerOfMass
116 Appliquer maintenant la formule classique de calcul du moment : calculer le produit
117 vectoriel par cellule de ``posSkin`` avec ``forceVectSkin`` (méthode ``DataArrayDouble.CrossProduct()``). ::
119 torquePerCellOnSkin = ml.DataArrayDouble.CrossProduct(posSkin,forceVectSkin)
121 Sommer ``torqueOnSkin`` en utilisant la méthode ``DataArrayDouble.accumulate()``. ::
123 zeTorque = torquePerCellOnSkin.accumulate()
124 print "couple = %r N.m" % zeTorque[2]
126 Vérifions le couple calculé précédemment en divisant la puissance par la vitesse *angulaire*.
127 La vitesse *linéaire* est stockée dans le champ "VITESSE_ELEM_DOM".
129 Calculer la puissance par cellule de la peau de l'agitateur et la sommer. ::
131 speedMts = data.getFields()["VITESSE_ELEM_DOM"]
132 speed1ts = speedMts[(2,-1)]
133 speedMc = speed1ts.getFieldAtLevel(ml.ON_CELLS,0)
134 speedOnSkin = speedMc.getArray()[tupleIdsInField]
135 powerSkin = ml.DataArrayDouble.Dot(forceVectSkin,speedOnSkin)
136 power = powerSkin.accumulate()[0]
137 print "power = %r W"%(power)
139 Calculer la vitesse *angulaire*. Pour ce faire, calculer la somme de ``x^2``, ``y^2`` et ``xz`` de ``posSkin`` et
140 construire (avec NumPy) la matrice 2x2 d'inertie ``inertiaSkin=[[x2,xy], [xy,z2]]``.
142 Récupérer le vecteur propre associé à la valeur propre maximale
143 avec ``linalg.eig(inertiaSkin)``. ::
145 x2 = posSkin[:,0]*posSkin[:,0]
146 x2 = x2.accumulate()[0]
147 y2 = posSkin[:,1]*posSkin[:,1]
148 y2 = y2.accumulate()[0]
149 xy = posSkin[:,0]*posSkin[:,1]
150 xy = xy.accumulate()[0]
151 inertiaSkin = np.matrix([[x2,xy],[xy,y2]])
152 inertiaSkinValues, inertiaSkinVects = np.linalg.eig(inertiaSkin)
153 pos = max(enumerate(inertiaSkinValues), key=lambda x: x[1])[0]
154 vect0 = inertiaSkinVects[pos].tolist()[0]
157 Grâce au calcul précédent on peut déduire que l'agitateur a tourné de 1.1183827931 radian (cf. solution complète pour le
158 détail - on remet les étapes précédentes dans une fonction que l'on applique sur plusieurs pas de temps).
160 Calculer et comparer le couple sur l'agitateur. ::
162 omega = 1.1183827931 / (ts[-1][2]-ts[0][2])
163 print "At timestep (%d,%d) (physical time=%r s) the torque is: %r N.m, power/omega=%r N.m " % (ts[2][0],ts[2][1],ts[2][2],zeTorque[2],power/omega)
168 :ref:`python_testmedcouplingloaderex1_solution`