Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Maillage (Mesh / SubMesh)

Le maillage de pyrucast se compose à deux niveaux, selon le motif agrégat / zone :

  • SubMesh : regroupe toutes les cellules d’un même ElementType. Stocke la connectivité à plat (un Vec<NodeId> de longueur cell_count × nodes_per_cell).
  • Mesh : agrège plusieurs SubMesh liés à la même Coords.

Le cas POI1 est volontairement dégénéré : un sous-maillage POI1 est exactement une liste de nœuds (un nœud par cellule), ce qui sert de support naturel aux NodeField.

Ce chapitre décrit l’objet maillage (structure, types d’éléments, refcount). La construction de maillages par des générateurs (line, triangulate_surface, extrude…) relève des opérateurs de maillage.

Types d’éléments

L’enum ElementType liste les types supportés ; chaque variante porte sa propre méta-information (nombre de nœuds, dimension topologique, nom court cast3m).

VarianteNœudsDim. topo.Cas usuel
POI110liste de nœuds
SEG221segment linéaire
TRI332triangle linéaire
QUA442quadrangle linéaire
TET443tétraèdre linéaire
PYRA553pyramide linéaire (raccord hexaèdre ↔ tétraèdre)
PENTA663prisme linéaire (extrusion d’un TRI3)
HEX883hexaèdre linéaire
SEG331segment quadratique
TRI662triangle quadratique
QUA882quadrangle quadratique (sérendipité)
QUA992quadrangle biquadratique (Lagrange complet, nœud central)
TET10103tétraèdre quadratique
PENTA15153prisme quadratique (sérendipité)
HEX20203hexaèdre quadratique (sérendipité)
HEX27273hexaèdre tri-quadratique (Lagrange complet, centres de face + nœud central)

Les huit derniers types sont quadratiques (Lagrange-2) : ils reprennent la numérotation des sommets de leur parent linéaire puis ajoutent les nœuds de milieu d’arête, dans l’ordre d’arêtes de la convention VTK (voir le rustdoc d’ElementType). QUA8, HEX20 et PENTA15 sont sérendipité (nœuds d’arête seulement) ; SEG3, TRI6, TET10, QUA9 et HEX27 sont des Lagrange complets (QUA9/HEX27 = quadrangle/hexaèdre bi-/tri-quadratiques, avec nœuds de face et central). Ils se posent avec l’interpolation LAGRANGE2 (cf. Espace éléments finis).

Ajouter un nouveau type d’élément est purement additif : un fichier src/atoms/element_kind/<nom>.rs et une variante — voir Ajouter un élément fini.

Cellule (Cell)

mesh.cell(submesh_idx, cell_idx) (ou mesh[submesh_idx][cell_idx]) renvoie une vue Cell sur une cellule : len(cell) donne son nombre de nœuds et cell[k] le k-ième Node. C’est l’accès lecture à la connectivité ; l’ajout passe par submesh.add_cell([...]).

Refcount sur les nœuds — interaction avec le GC

Chaque appel à SubMesh::add_cell incrémente le refcount interne de chaque nœud dans la Coords (cf. Coordonnées). Le Drop du SubMesh les décrémente. Tant qu’un SubMesh référence un nœud, le ramasse-miettes le protège — même si tous les Node utilisateurs ont disparu.

   Coords             ◀── refcount par NodeId
        │                          (le nœud est-il vivant ?)
        ├── Node(s) utilisateur(s) ── chacun +1
        └── SubMesh(s)             ── chacun +1 par cellule incidente

En cas d’échec partiel d’add_cell (par exemple un nœud déjà ramassé), les incréments déjà effectués pour la cellule courante sont annulés (rollback transactionnel à l’échelle d’une cellule).

Construire en bloc. Un add_cell prend le verrou d’écriture de la Coords et lâche les caches dérivés du sous-maillage : c’est le bon grain pour une maille posée à la main, mais sur un million de mailles c’est ce prix-là qu’on paie, pas la connectivité. Un opérateur qui produit un gros maillage passe donc par SubMesh::from_connectivity(coords, type, connectivité) (côté Rust), qui valide et incrémente tout le tableau en une seule prise de verrou — une unité par occurrence, comme toujours — et par Coords::add_nodes pour créer ses nœuds d’un coup. Tous les opérateurs de maillage l’empruntent : les balayages (sweep, extrude, revolve, sweep_solid), transfinite, les copies rigides (translate, rotate, symétries, copy), merge_nodes, convert, to_quadratic, skin, border, chain, orient, select, consolidate, barycenter, to_poi1, la lecture gmsh et les mailleurs frontaux. Un nœud dont la position n’est pas encore arrêtée — celle qu’un front déplace tant qu’il avance, celle qu’un relâchement va lisser — n’est même plus créé avant de l’être : il vit comme un rang dans un tableau, et ne devient un NodeId qu’à la fin.

Scellement (connectivité figée après consommation)

Par convention, un maillage n’est plus modifié une fois construit : un espace éléments finis, un champ ou une matrice indexent ses cellules, et lui ajouter une maille par la suite les laisserait dans un état incohérent.

Cette convention est désormais imposée. Dès qu’un objet autre qu’un maillage capture un SubMesh et indexe ses cellules (construction d’un SubFiniteElementSpace, d’un support de SubMatrix…), ce sous-maillage est scellé : sa connectivité est figée pour toujours. add_cell / add_cell_taking renvoient alors l’erreur MeshSealed. Un Mesh qui se contente de contenir le sous-maillage ne le scelle pas — il peut continuer à grossir tant qu’aucun consommateur ne s’y attache. On teste l’état avec is_sealed.

Un champ aux nœuds fait exception, parce qu’il n’indexe pas les cellules du maillage : il se pose sur le compagnon POI1 de la zone, et c’est ce compagnon-là qui est scellé. Le maillage donné en argument, lui, reste modifiable ; le modifier lâche son compagnon, si bien qu’un champ construit ensuite se retrouve sur un autre support — les champs déjà construits ne sont pas invalidés pour autant, ils restent définis sur le nuage de nœuds d’avant (voir Champ aux nœuds).

Pour repartir d’un maillage scellé et le modifier à nouveau, on en prend une copie profonde avec duplicate() : un SubMesh (ou Mesh) neuf, non scellé, avec la même connectivité (les nœuds sont partagés — même Coords —, seuls leurs refcounts augmentent). L’opérateur mesh.copy(m, new_nodes=…) est la même chose au niveau Mesh, avec le choix supplémentaire de recréer des nœuds neufs aux mêmes endroits plutôt que de partager ceux de l’original.

mesh = pyrucast.Mesh(c, "TRI3")
mesh.unit().add_cell([a, b, n3])

pyrucast.FiniteElementSpace(mesh)  # scelle mesh[0]
assert mesh[0].is_sealed
# mesh[0].add_cell([...])           # → RuntimeError (MeshSealed)

copie = mesh.duplicate()  # neuf, modifiable
copie.unit().add_cell([b, n3, n4])  # OK

API Rust

#[test]
fn un_maillage_se_compose_de_zones() -> Result<()> {
    let coords = Handle::new(Coords::new(2)?);
    let a = Node::create_in(coords.clone(), &[0.0, 0.0])?;
    let b = Node::create_in(coords.clone(), &[1.0, 0.0])?;
    let c = Node::create_in(coords.clone(), &[0.5, 1.0])?;

    let mut sm = SubMesh::new(coords.clone(), ElementType::TRI3);
    sm.add_cell(&[a.id(), b.id(), c.id()])?;

    let sm_handle = Handle::new(sm);
    let mut mesh = Mesh::empty(); // l'agrégat ne porte pas la `Coords`
    mesh.add_sub(sm_handle)?;
    assert_eq!(mesh.cell_count(), 1);
    Ok(())
}

API Python

import pyrucast

c = pyrucast.Coords(dim=2)
a = c.add_node([0.0, 0.0])
b = c.add_node([1.0, 0.0])
n3 = c.add_node([0.5, 1.0])

# Mesh(coords, type) creates a mesh with a single submesh; unit() gives the
# view of it, add_cell adds a cell.
mesh = pyrucast.Mesh(c, "TRI3")
mesh.unit().add_cell([a, b, n3])
print(mesh)  # Mesh: 1 submesh(es), 1 cell(s) total
print(mesh.element_types())  # ['TRI3']
print(mesh.cell_counts())  # [1]

# Composer plusieurs zones : l'union | (jamais +).
quad = pyrucast.Mesh(c, "QUA4")
# … add_cell … ;  combined = mesh | quad

Durée de vie et refcount

Les SubMesh et Mesh portent un effet de bord dans leur Drop : ils décrémentent le refcount des nœuds qu’ils référencent dans la Coords. Cet effet a lieu exactement une fois, quand le dernier Handle sur le maillage disparaît. Le détail est dans le chapitre Modèle mémoire.

Visualisation

mesh.plot(...) trace le maillage (chaque sous-maillage avec sa propre couleur, ou coloré par un champ). Voir Visualisation.