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

Champ aux points de Gauss (ElementField / SubElementField)

Un champ aux points de Gauss porte une ou plusieurs valeurs par (cellule, point de Gauss) sur un espace éléments finis. Il suit la même grammaire d’agrégat que tous les conteneurs de pyrucast (cf. Agrégat) et le contrat de champ :

  • SubElementField — les valeurs d’une zone : un bloc multi-composantes sur les cellules × points de Gauss d’un SubFiniteElementSpace ;
  • ElementField — l’agrégat : une liste de SubElementField, un par sous-espace, avec éventuellement des composantes différentes d’une zone à l’autre.

C’est le miroir exact de NodeField côté points d’intégration. C’est l’objet sur lequel s’écrivent naturellement :

  • les propriétés matériau (module d’Young, Poisson, conductivité, masse volumique…) évaluées là où les intégrales sont calculées ;
  • les variables internes (déformation plastique, endommagement…) gardées de cellule en cellule et de point en point ;
  • les grandeurs dérivées d’une solution (contraintes, déformations, flux…) pour le post-traitement, produites par les opérateurs de champ (gradient, deformation…) et le comportement (integrate_behavior).

Support : un sous-espace éléments finis par zone

Chaque SubElementField est attaché à un seul SubFiniteElementSpace (cf. Espace éléments finis), qui détermine :

  • la liste des cellules concernées (via son SubMesh) ;
  • le nombre de points de Gauss par cellule (via sa QuadratureRule).

Les trois dimensions d’une zone sont figées à la construction : cell_count (du SubMesh), gauss_count (de la quadrature), component_count (choisi par l’utilisateur). Le buffer interne est dimensionné une fois pour toutes et n’est jamais réalloué — la topologie du maillage sous-jacent doit rester figée pour la durée de vie du champ (les coordonnées, elles, peuvent évoluer ; cf. FiniteElementSpace).

Les coordonnées et poids des points de Gauss ne sont pas stockés dans le champ : ils restent sur le SubFiniteElementSpace comme données de référence.

   ElementField (agrégat)
   ├── SubElementField zone 0 ── support SubFiniteElementSpace ── values[…]
   ├── SubElementField zone 1 ── support SubFiniteElementSpace ── values[…]
   └── …

Composantes nommées

Chaque zone porte ses noms de composantes ("E", "nu", "sigma_xx", "plastic_strain"…) : au moins une, noms uniques, valeurs initialisées à 0.0. Au niveau agrégat, components() renvoie l’union des composantes des zones (ordre de première apparition) ; une composante peut n’exister que sur certaines zones.

Disposition mémoire

Les valeurs d’une zone sont rangées à plat, ligne-major, dans l’ordre cellule → gauss → composante :

values[cell_idx * gauss_count * component_count
       + g * component_count
       + c]

Cet ordre rend deux accès courants cache-friendly :

  • lire toutes les composantes à un point de Gauss d’une cellule (par exemple (E, nu, rho) pendant l’assemblage) — component_count flottants contigus, exposés par point_values(cell, g) ;
  • balayer tous les points de Gauss d’une cellule pour une composante donnée — gauss_count flottants régulièrement espacés.

Construction : au niveau agrégat

Comme tout agrégat, un ElementField se construit au niveau parent, à partir d’un FiniteElementSpace : une zone par sous-espace.

  • ElementField(fes, components) — la même liste de composantes pour chaque sous-espace ;
  • ElementField.with_components_per_subspace(fes, [...]) — une liste de composantes par sous-espace (multiphysique / multi-matériau).

Pour fabriquer un champ matériau prêt pour l’assemblage, l’opérateur material_field(model, [...]) est plus direct : il crée les zones nécessaires aux sous-modèles qui consomment du matériau et les remplit en un appel.

Refcount et cycle de vie

Chaque zone détient un Handle<SubFiniteElementSpace> (cloné, donc compté). Tant qu’une zone est vivante, son sous-espace ne peut pas être collecté ; à son Drop, le refcount du sous-espace décroît et la cascade descend jusqu’au SubMesh puis à la Coords. Un ElementField n’incrémente pas le refcount des nœuds : il n’a pas de support nodal direct — les nœuds restent protégés par le SubMesh du sous-espace.

API Rust

#[test]
fn un_champ_aux_points_de_gauss_porte_le_materiau() -> 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.0, 1.0])?;
    let mut mesh = Mesh::from_submesh(SubMesh::new(coords, ElementType::TRI3));
    mesh.add_cell(&[a.id(), b.id(), c.id()])?;
    let fes = FiniteElementSpace::lagrange1(&mesh)?;

    // 2-D linear elasticity: two material properties, one zone (one subspace).
    let mat = ElementField::new(&fes, vec!["E".into(), "nu".into()])?;
    {
        let mut z = mat.get(0)?.write(); // la zone (SubElementField) — guard
        z.set_uniform("E", 210e9)?; // module d'Young constant
        z.set_uniform("nu", 0.3)?; // Poisson constant
        assert_eq!(z.value(0, 0, "E")?, 210e9);
    }

    // Components per subspace (multi-material):
    let mat2 = ElementField::with(
        &fes,
        &[vec!["E".into(), "nu".into()]], // une liste par sous-espace
    )?;

    // Statistics and arithmetic at the aggregate level.
    assert_eq!(Field::max(&mat, Some("E"))?, 210e9);
    let scaled = &mat * 1.1; // nouveau champ (référence : préserve `mat`)
    mat.mul_to_component("E", 0.95)?; // en place, seulement "E"
    let _ = (mat2, scaled);
    Ok(())
}

API Python

import pyrucast

# Maillage + FE space — préparation.
c = pyrucast.Coords(dim=2)
a = c.add_node([0.0, 0.0])
b = c.add_node([1.0, 0.0])
c2 = c.add_node([0.0, 1.0])
mesh = pyrucast.Mesh(c, "TRI3")
mesh.unit().add_cell([a, b, c2])
fes = pyrucast.FiniteElementSpace(mesh)

# Material field: one zone per subspace of `fes`.
mat = pyrucast.ElementField(fes, ["E", "nu"])
print(mat)  # ElementField: 1 subfield(s)
print(mat.unit())  # SubElementField: 1 cell(s) × 3 gauss × 2 component(s) [E, nu]

# Writing through the zone; reading through the zone (or the aggregate stats).
z = mat.unit()  # the only zone (an error if there were several)
z.set_uniform("E", 210e9)
z.set_uniform("nu", 0.3)
assert z.value(0, 0, "E") == 210e9

# Dictionary-like access on the zone — `sub[cell, gauss, "name"]`.
z[0, 2, "nu"] = 0.28
assert z[0, 2, "nu"] == 0.28

# Stats and arithmetic at the aggregate level.
print(mat.min("E"), mat.max("E"))  # 210000000000.0 210000000000.0
print(mat.sum("E"))  # Σ over the Gauss points
mat.mul_to_component("E", 0.95)  # en place, seulement "E"
scaled = mat * 1.1  # nouveau champ

# Components per subspace (multiphysics / multi-material).
ef = pyrucast.ElementField.with_components_per_subspace(fes, [["E", "nu"]])
print(ef.components())  # ['E', 'nu']

Le plus souvent, on ne construit pas le champ matériau à la main : on appelle l’opérateur material_field qui apparie les zones aux sous-modèles consommateurs de matériau et ignore les autres (Dirichlet, …).

Visualisation

element_field.plot(...) colore le champ sur son propre support : chaque zone retrouve son sous-maillage via son sous-espace EF (partagé, pas copié). Le rendu raisonne par élément — les valeurs nodales viennent d’un moindre carré local à l’élément sur les valeurs de Gauss, sans moyenne inter-éléments, de sorte que les discontinuités (flux, contraintes) restent visibles. Voir Visualisation.

Sérialisation

SubElementField (et donc ElementField) implémente Portable via serde comme tous les objets pyrucast : le buffer de valeurs et la liste de noms voyagent dans le format binaire portable Linux ↔ Windows. Le lien vers l’espace EF, lui, est un Handle : il devient un identifiant local au fichier, et l’espace est écrit avec le champ — voir Sauvegarde et relecture.

Limitations actuelles

  • Pas de mécanisme de rééchantillonnage entre quadratures (« projeter ce champ Gauss-2-points sur un autre Gauss-3-points ») : le sous-espace est figé à la création de chaque zone.
  • L’arithmétique binaire entre champs est stricte (même support, mêmes composantes ; cf. Champ) — il n’y a pas (encore) de combinaison tolérante avec rééchantillonnage ou complétion par zéro.