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’unSubFiniteElementSpace;ElementField— l’agrégat : une liste deSubElementField, 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_countflottants contigus, exposés parpoint_values(cell, g); - balayer tous les points de Gauss d’une cellule pour une composante donnée
—
gauss_countflottants 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.